Polski

Opanuj bezpieczne typowo wywołania API w TypeScript, aby tworzyć solidne, łatwe w utrzymaniu i wolne od błędów aplikacje internetowe. Poznaj najlepsze praktyki i zaawansowane techniki.

Bezpieczne typowo wywołania API w TypeScript: Kompleksowy przewodnik

We współczesnym tworzeniu aplikacji internetowych interakcja z API jest fundamentalnym zadaniem. TypeScript, dzięki swojemu potężnemu systemowi typów, oferuje znaczącą przewagę w zapewnianiu niezawodności i łatwości utrzymania aplikacji, umożliwiając bezpieczne typowo wywołania API. Ten przewodnik pokaże, jak wykorzystać funkcje TypeScript do budowania solidnych i wolnych od błędów interakcji z API, omawiając najlepsze praktyki, zaawansowane techniki i przykłady z życia wzięte.

Dlaczego bezpieczeństwo typów ma znaczenie przy wywołaniach API

Pracując z API, w gruncie rzeczy mamy do czynienia z danymi pochodzącymi z zewnętrznego źródła. Dane te nie zawsze mogą mieć oczekiwany format, co prowadzi do błędów w czasie wykonania i nieoczekiwanego zachowania. Bezpieczeństwo typów zapewnia kluczową warstwę ochrony, weryfikując, czy otrzymane dane odpowiadają predefiniowanej strukturze, co pozwala wychwycić potencjalne problemy na wczesnym etapie procesu deweloperskiego.

Konfiguracja projektu TypeScript

Zanim zagłębisz się w wywołania API, upewnij się, że masz skonfigurowany projekt TypeScript. Jeśli zaczynasz od zera, możesz zainicjować nowy projekt za pomocą:

npm init -y
npm install typescript --save-dev
tsc --init

Spowoduje to utworzenie pliku `tsconfig.json` z domyślnymi opcjami kompilatora TypeScript. Możesz dostosować te opcje do potrzeb swojego projektu. Na przykład, możesz chcieć włączyć tryb `strict` dla bardziej rygorystycznego sprawdzania typów:

// tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

Definiowanie typów dla odpowiedzi API

Pierwszym krokiem do osiągnięcia bezpiecznych typowo wywołań API jest zdefiniowanie typów TypeScript, które reprezentują strukturę danych, jakich oczekujemy od API. Zazwyczaj robi się to za pomocą deklaracji `interface` lub `type`.

Używanie interfejsów

Interfejsy to potężny sposób na definiowanie kształtu obiektu. Na przykład, jeśli pobierasz listę użytkowników z API, możesz zdefiniować interfejs w ten sposób:

interface User {
  id: number;
  name: string;
  email: string;
  address?: string; // Właściwość opcjonalna
  phone?: string; // Właściwość opcjonalna
  website?: string; // Właściwość opcjonalna
  company?: {
    name: string;
    catchPhrase: string;
    bs: string;
  };
}

Znak `?` po nazwie właściwości wskazuje, że jest ona opcjonalna. Jest to przydatne do obsługi odpowiedzi API, w których niektóre pola mogą być nieobecne.

Używanie typów

Typy są podobne do interfejsów, ale oferują większą elastyczność, w tym możliwość definiowania typów unii i typów przecięć. Ten sam rezultat, co w powyższym interfejsie, można osiągnąć za pomocą typu:

type User = {
  id: number;
  name: string;
  email: string;
  address?: string; // Właściwość opcjonalna
  phone?: string; // Właściwość opcjonalna
  website?: string; // Właściwość opcjonalna
  company?: {
    name: string;
    catchPhrase: string;
    bs: string;
  };
};

W przypadku prostych struktur obiektowych interfejsy i typy są często wymienne. Jednak typy stają się potężniejsze w przypadku bardziej złożonych scenariuszy.

Wykonywanie wywołań API za pomocą Axios

Axios to popularny klient HTTP do wykonywania zapytań API w JavaScript i TypeScript. Zapewnia czyste i intuicyjne API, ułatwiając obsługę różnych metod HTTP, nagłówków zapytań i danych odpowiedzi.

Instalacja Axios

npm install axios

Wykonywanie typowanego wywołania API

Aby wykonać bezpieczne typowo wywołanie API za pomocą Axios, możesz użyć metody `axios.get` i określić oczekiwany typ odpowiedzi za pomocą typów generycznych:

import axios from 'axios';

async function fetchUsers(): Promise {
  try {
    const response = await axios.get('https://jsonplaceholder.typicode.com/users');
    return response.data;
  } catch (error) {
    console.error('Błąd podczas pobierania użytkowników:', error);
    throw error;
  }
}

fetchUsers().then(users => {
  users.forEach(user => {
    console.log(user.name);
  });
});

W tym przykładzie `axios.get('...')` informuje TypeScript, że dane odpowiedzi powinny być tablicą obiektów `User`. Pozwala to TypeScriptowi na sprawdzanie typów i autouzupełnianie podczas pracy z danymi odpowiedzi.

Obsługa różnych metod HTTP

Axios obsługuje różne metody HTTP, w tym `GET`, `POST`, `PUT`, `DELETE` i `PATCH`. Możesz używać odpowiednich metod do wykonywania różnych typów zapytań API. Na przykład, aby utworzyć nowego użytkownika, możesz użyć metody `axios.post`:

async function createUser(user: Omit): Promise {
  try {
    const response = await axios.post('https://jsonplaceholder.typicode.com/users', user);
    return response.data;
  } catch (error) {
    console.error('Błąd podczas tworzenia użytkownika:', error);
    throw error;
  }
}

const newUser = {
  name: 'John Doe',
  email: 'john.doe@example.com',
  address: '123 Main St',
  phone: '555-1234',
  website: 'example.com',
  company: {
    name: 'Example Corp',
    catchPhrase: 'Leading the way',
    bs: 'Innovative solutions'
  }
};

createUser(newUser).then(user => {
  console.log('Utworzono użytkownika:', user);
});

W tym przykładzie `Omit` tworzy typ, który jest taki sam jak `User`, ale bez właściwości `id`. Jest to przydatne, ponieważ `id` jest zazwyczaj generowane przez serwer podczas tworzenia nowego użytkownika.

Korzystanie z Fetch API

Fetch API to wbudowane w JavaScript API do wykonywania zapytań HTTP. Chociaż jest bardziej podstawowe niż Axios, można go również używać z TypeScriptem, aby uzyskać bezpieczne typowo wywołania API. Możesz go preferować, aby uniknąć dodawania zależności, jeśli odpowiada to twoim potrzebom.

Wykonywanie typowanego wywołania API za pomocą Fetch

Aby wykonać bezpieczne typowo wywołanie API za pomocą Fetch, możesz użyć funkcji `fetch`, a następnie sparsować odpowiedź jako JSON, określając oczekiwany typ odpowiedzi:

async function fetchUsers(): Promise {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    if (!response.ok) {
      throw new Error(`Błąd HTTP! status: ${response.status}`);
    }
    const data: User[] = await response.json();
    return data;
  } catch (error) {
    console.error('Błąd podczas pobierania użytkowników:', error);
    throw error;
  }
}

fetchUsers().then(users => {
  users.forEach(user => {
    console.log(user.name);
  });
});

W tym przykładzie `const data: User[] = await response.json();` informuje TypeScript, że dane odpowiedzi powinny być traktowane jako tablica obiektów `User`. Pozwala to TypeScriptowi na przeprowadzanie sprawdzania typów i autouzupełniania.

Obsługa różnych metod HTTP za pomocą Fetch

Aby wykonywać różne rodzaje zapytań API za pomocą Fetch, możesz użyć funkcji `fetch` z różnymi opcjami, takimi jak `method` i `body`. Na przykład, aby utworzyć nowego użytkownika, możesz użyć następującego kodu:

async function createUser(user: Omit): Promise {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(user)
    });
    if (!response.ok) {
      throw new Error(`Błąd HTTP! status: ${response.status}`);
    }
    const data: User = await response.json();
    return data;
  } catch (error) {
    console.error('Błąd podczas tworzenia użytkownika:', error);
    throw error;
  }
}

const newUser = {
  name: 'John Doe',
  email: 'john.doe@example.com',
  address: '123 Main St',
  phone: '555-1234',
  website: 'example.com',
  company: {
    name: 'Example Corp',
    catchPhrase: 'Leading the way',
    bs: 'Innovative solutions'
  }
};

createUser(newUser).then(user => {
  console.log('Utworzono użytkownika:', user);
});

Obsługa błędów API

Obsługa błędów jest krytycznym aspektem wywołań API. API mogą zawieść z wielu powodów, w tym z powodu problemów z łącznością sieciową, błędów serwera i nieprawidłowych zapytań. Niezbędne jest eleganckie obsłużenie tych błędów, aby zapobiec awarii aplikacji lub wyświetlaniu nieoczekiwanych zachowań.

Używanie bloków Try-Catch

Najczęstszym sposobem obsługi błędów w kodzie asynchronicznym jest użycie bloków try-catch. Pozwala to na przechwycenie wszelkich wyjątków, które są rzucane podczas wywołania API i odpowiednie ich obsłużenie.

async function fetchUsers(): Promise {
  try {
    const response = await axios.get('https://jsonplaceholder.typicode.com/users');
    return response.data;
  } catch (error) {
    console.error('Błąd podczas pobierania użytkowników:', error);
    // Obsłuż błąd, np. wyświetl komunikat o błędzie użytkownikowi
    throw error; // Rzuć błąd ponownie, aby kod wywołujący również mógł go obsłużyć
  }
}

Obsługa konkretnych kodów błędów

API często zwracają określone kody błędów, aby wskazać typ błędu, który wystąpił. Możesz użyć tych kodów błędów, aby zapewnić bardziej szczegółową obsługę błędów. Na przykład, możesz chcieć wyświetlić inny komunikat o błędzie dla błędu 404 Not Found niż dla błędu 500 Internal Server Error.

async function fetchUser(id: number): Promise {
  try {
    const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${id}`);
    return response.data;
  } catch (error: any) {
    if (error.response?.status === 404) {
      console.log(`Użytkownik o ID ${id} nie został znaleziony.`);
      return null; // Lub rzuć niestandardowy błąd
    } else {
      console.error('Błąd podczas pobierania użytkownika:', error);
      throw error;
    }
  }
}

fetchUser(123).then(user => {
  if (user) {
    console.log('Użytkownik:', user);
  } else {
    console.log('Nie znaleziono użytkownika.');
  }
});

Tworzenie niestandardowych typów błędów

W bardziej złożonych scenariuszach obsługi błędów można tworzyć niestandardowe typy błędów, aby reprezentować różne rodzaje błędów API. Pozwala to na dostarczanie bardziej ustrukturyzowanych informacji o błędach i ich skuteczniejszą obsługę.

class ApiError extends Error {
  constructor(public statusCode: number, message: string) {
    super(message);
    this.name = 'ApiError';
  }
}

async function fetchUser(id: number): Promise {
  try {
    const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${id}`);
    return response.data;
  } catch (error: any) {
    if (error.response?.status === 404) {
      throw new ApiError(404, `Użytkownik o ID ${id} nie został znaleziony.`);
    } else {
      console.error('Błąd podczas pobierania użytkownika:', error);
      throw new ApiError(500, 'Wewnętrzny błąd serwera'); //Lub inny odpowiedni kod statusu
    }
  }
}

fetchUser(123).catch(error => {
  if (error instanceof ApiError) {
    console.error(`Błąd API: ${error.statusCode} - ${error.message}`);
  } else {
    console.error('Wystąpił nieoczekiwany błąd:', error);
  }
});

Walidacja danych

Nawet z systemem typów TypeScript, kluczowe jest walidowanie danych otrzymywanych z API w czasie wykonania. API mogą zmieniać swoją strukturę odpowiedzi bez uprzedzenia, a twoje typy w TypeScript nie zawsze mogą być idealnie zsynchronizowane z rzeczywistą odpowiedzią API.

Używanie Zod do walidacji w czasie wykonania

Zod to popularna biblioteka TypeScript do walidacji danych w czasie wykonania. Pozwala definiować schematy, które opisują oczekiwaną strukturę danych, a następnie walidować dane względem tych schematów w czasie wykonania.

Instalacja Zod

npm install zod

Walidacja odpowiedzi API za pomocą Zod

Aby walidować odpowiedzi API za pomocą Zod, można zdefiniować schemat Zod, który odpowiada typowi TypeScript, a następnie użyć metody `parse` do walidacji danych.

import { z } from 'zod';

const userSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  address: z.string().optional(),
  phone: z.string().optional(),
  website: z.string().optional(),
  company: z.object({
    name: z.string(),
    catchPhrase: z.string(),
    bs: z.string(),
  }).optional(),
});

type User = z.infer;

async function fetchUsers(): Promise {
  try {
    const response = await axios.get('https://jsonplaceholder.typicode.com/users');
    const data = z.array(userSchema).parse(response.data);
    return data;
  } catch (error) {
    console.error('Błąd podczas pobierania użytkowników:', error);
    throw error;
  }
}

W tym przykładzie, `z.array(userSchema).parse(response.data)` waliduje, czy dane odpowiedzi są tablicą obiektów zgodnych z `userSchema`. Jeśli dane nie są zgodne ze schematem, Zod rzuci błąd, który można następnie odpowiednio obsłużyć.

Zaawansowane techniki

Używanie typów generycznych dla reużywalnych funkcji API

Typy generyczne pozwalają pisać reużywalne funkcje API, które mogą obsługiwać różne typy danych. Na przykład, można utworzyć generyczną funkcję `fetchData`, która może pobierać dane z dowolnego punktu końcowego API i zwracać je z poprawnym typem.

async function fetchData(url: string): Promise {
  try {
    const response = await axios.get(url);
    return response.data;
  } catch (error) {
    console.error(`Błąd podczas pobierania danych z ${url}:`, error);
    throw error;
  }
}

// Użycie
fetchData('https://jsonplaceholder.typicode.com/users').then(users => {
  console.log('Użytkownicy:', users);
});

fetchData<{ title: string; body: string }>('https://jsonplaceholder.typicode.com/todos/1').then(todo => {
    console.log('Zadanie', todo)
});

Używanie interceptorów do globalnej obsługi błędów

Axios dostarcza interceptory, które pozwalają przechwytywać zapytania i odpowiedzi, zanim zostaną one obsłużone przez kod. Można użyć interceptorów do implementacji globalnej obsługi błędów, takiej jak logowanie błędów lub wyświetlanie komunikatów o błędach użytkownikowi.

axios.interceptors.response.use(
  (response) => response,
  (error) => {
    console.error('Globalny handler błędów:', error);
    // Wyświetl komunikat o błędzie użytkownikowi
    return Promise.reject(error);
  }
);

Używanie zmiennych środowiskowych dla adresów URL API

Aby uniknąć umieszczania na stałe adresów URL API w kodzie, można użyć zmiennych środowiskowych do ich przechowywania. Ułatwia to konfigurację aplikacji dla różnych środowisk, takich jak deweloperskie, stagingowe i produkcyjne.

Przykład użycia pliku `.env` i pakietu `dotenv`.

// .env
API_URL=https://api.example.com
// Zainstaluj dotenv
npm install dotenv
// Importuj i skonfiguruj dotenv
import * as dotenv from 'dotenv'
dotenv.config()

const apiUrl = process.env.API_URL || 'http://localhost:3000'; // podaj wartość domyślną

async function fetchData(endpoint: string): Promise {
  try {
    const response = await axios.get(`${apiUrl}/${endpoint}`);
    return response.data;
  } catch (error) {
    console.error(`Błąd podczas pobierania danych z ${apiUrl}/${endpoint}:`, error);
    throw error;
  }
}

Podsumowanie

Bezpieczne typowo wywołania API są niezbędne do budowania solidnych, łatwych w utrzymaniu i wolnych od błędów aplikacji internetowych. TypeScript dostarcza potężnych funkcji, które umożliwiają definiowanie typów dla odpowiedzi API, walidację danych w czasie wykonania i elegancką obsługę błędów. Stosując najlepsze praktyki i techniki przedstawione w tym przewodniku, można znacznie poprawić jakość i niezawodność interakcji z API.

Używając TypeScript i bibliotek takich jak Axios i Zod, możesz zapewnić, że twoje wywołania API są bezpieczne typowo, dane są walidowane, a błędy obsługiwane w elegancki sposób. Prowadzi to do tworzenia bardziej solidnych i łatwiejszych w utrzymaniu aplikacji.

Pamiętaj, aby zawsze walidować dane w czasie wykonania, nawet przy użyciu systemu typów TypeScript. API mogą się zmieniać, a twoje typy nie zawsze mogą być idealnie zsynchronizowane z rzeczywistą odpowiedzią API. Walidując dane w czasie wykonania, możesz wychwycić potencjalne problemy, zanim spowodują one kłopoty w twojej aplikacji.

Miłego kodowania!

Bezpieczne typowo wywołania API w TypeScript: Kompleksowy przewodnik | MLOG