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.
- Mniej błędów w czasie wykonania: Sprawdzanie typów w czasie kompilacji pomaga zidentyfikować i naprawić błędy związane z typami, zanim trafią na produkcję.
- Lepsza łatwość utrzymania kodu: Jasne definicje typów sprawiają, że kod jest łatwiejszy do zrozumienia i modyfikacji, zmniejszając ryzyko wprowadzenia błędów podczas refaktoryzacji.
- Zwiększona czytelność kodu: Adnotacje typów stanowią cenną dokumentację, ułatwiając deweloperom zrozumienie oczekiwanych struktur danych.
- Lepsze doświadczenie deweloperskie: Wsparcie IDE dla sprawdzania typów i autouzupełniania znacznie poprawia doświadczenie dewelopera i zmniejsza prawdopodobieństwo błędów.
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
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
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!