Paweł Łukasiewicz
2024-12-02
Paweł Łukasiewicz
2024-12-02
Udostępnij Udostępnij Kontakt
Wprowadzenie

Kiedy aplikacje korzystają z zewnętrznych API, dane mogą przychodzić w różnych formatach i strukturach, a brak odpowiedniej walidacji i typowania może prowadzić do błędów i trudności w utrzymaniu kodu. Dzięki TypeScript możemy określać typy danych z API, co pozwala nam pisać bardziej niezawodny, bezpieczny i przewidywalny kod.

W tym wpisie omówimy:

  1. Definiowanie typów na podstawie danych API.
  2. Typowanie złożonych struktur danych.
  3. Walidacja danych z API.
  4. Zarządzanie błędami typów z API.
  5. Generowanie typów dla API za pomocą narzędzi.

1. Definiowanie typów na podstawie danych API

Najczęstszym przypadkiem pracy z API jest odbieranie danych w formacie JSON. W takim przypadku TypeScript umożliwia nam utworzenie odpowiednich interfejsów, które odzwierciedlają strukturę danych. Dzięki temu mamy jasność, jak dane będą wyglądały i możemy sprawdzić, czy aplikacja odbiera odpowiednie typy.

Przykład danych JSON

Załóżmy, że API zwraca dane użytkownika w poniższym formacie:

{
  "id": 1,
  "username": "john_doe",
  "email": "john@example.com",
  "isActive": true
}
Możemy stworzyć interfejs TypeScript, który odwzoruje tę strukturę:
interface User {
  id: number;
  username: string;
  email: string;
  isActive: boolean;
}
Następnie w funkcji, która pobiera dane z API, możemy zwrócić obiekt o typie User
const fetchUser = async (userId: number): Promise<User> => {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  const data: User = await response.json();
  
  return data;
};
Dzięki temu mamy pewność, że dane odbierane z API są zgodne z typem User, a każde niezgodne dane spowodują ostrzeżenie już na etapie kompilacji.

2. Typowanie złożonych struktur danych

API często zwraca złożone struktury danych, takie jak tablice obiektów, zagnieżdżone obiekty czy dynamicznie zmieniające się właściwości. W takich przypadkach TypeScript pozwala precyzyjnie określić typy złożone, co znacznie poprawia czytelność i bezpieczeństwo kodu.

Przykład złożonych danych

Jeśli API zwraca listę użytkowników:

{
  "users": [
    { "id": 1, "username": "john_doe", "email": "john@example.com", "isActive": true },
    { "id": 2, "username": "jane_doe", "email": "jane@example.com", "isActive": false }
  ]
}
Możemy zdefiniować typ tablicy użytkowników:
interface UserList {
  users: User[];
}

const fetchUsers = async (): Promise<UserList> => {
  const response = await fetch('https://api.example.com/users');
  const data: UserList = await response.json();
  
  return data;
};
Typowanie złożonych danych pozwala na bezproblemową pracę z tablicami, zagnieżdżonymi obiektami i bardziej skomplikowanymi strukturami, jakie często spotykamy w rzeczywistych aplikacjach.

3. Walidacja danych z API

Mimo że TypeScript pomaga w typowaniu, dane z zewnętrznych API nie zawsze są zgodne z oczekiwaniami. Dlatego ważne jest, aby walidować dane, które przychodzą z API, zanim zaczniemy je przetwarzać. Możemy to zrobić, pisząc funkcje walidujące, które sprawdzą, czy dane spełniają oczekiwane warunki.

Walidacja danych

Załóżmy, że chcemy upewnić się, że obiekt User ma wszystkie wymagane właściwości:

const isValidUser = (data: any): data is User => {
  return (
    typeof data.id === 'number' &&
    typeof data.username === 'string' &&
    typeof data.email === 'string' &&
    typeof data.isActive === 'boolean'
  );
};

const fetchUser = async (userId: number): Promise<User | null> => {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  const data = await response.json();

  if (isValidUser(data)) {
    return data;
  } else {
    console.error('Invalid data from API');
    return null;
  }
};
Dzięki walidacji mamy pewność, że dane, które przetwarzamy, rzeczywiście pasują do zdefiniowanego interfejsu User.

4. Zarządzanie błędami typów z API

Zarządzanie błędami pochodzącymi z API jest kluczowe w przypadku pracy z danymi, które mogą nie spełniać oczekiwanych standardów. Czasami dane mogą być niekompletne lub struktura API może się zmienić. TypeScript może pomóc w tworzeniu elastycznych typów, które uwzględniają te zmienne.

Możemy używać opcjonalnych pól lub typów złożonych, aby poprawnie zarządzać niepewnymi danymi:

interface User {
  id: number;
  username: string;
  email?: string;  // email może być opcjonalny
  isActive: boolean;
}
Jeśli dane przychodzące z API mogą nie zawierać wszystkich pól, możemy użyć typu Partial, aby wszystkie pola były opcjonalne:
const fetchUser = async (userId: number): Promise<Partial<User>> => {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  const data: Partial<User> = await response.json();

  return data;
};
Dzięki temu mamy większą elastyczność w obsłudze danych o nieprzewidywalnej strukturze.

5. Generowanie typów dla API za pomocą narzędzi

W dużych projektach, gdzie API jest rozbudowane i często aktualizowane, ręczne tworzenie i utrzymywanie typów może być czasochłonne i podatne na błędy. W takich przypadkach warto skorzystać z narzędzi, które automatycznie generują typy na podstawie schematów API, takich jak OpenAPI (Swagger) lub GraphQL.

Popularne narzędzia do generowania typów:

  • swagger-typescript-api – generuje typy na podstawie specyfikacji OpenAPI.
  • graphql-codegen – generuje typy na podstawie schematów GraphQL.

Takie narzędzia pozwalają zaoszczędzić czas i zmniejszają ryzyko błędów, automatycznie aktualizując typy, gdy schemat API się zmienia.

Podsumowanie

Tworzenie typów dla danych przychodzących z API w TypeScript znacząco poprawia jakość kodu, zapewniając większą przewidywalność, stabilność i bezpieczeństwo. Definiowanie typów, walidacja danych oraz korzystanie z narzędzi do generowania typów pozwalają programistom skutecznie zarządzać danymi pochodzącymi z zewnętrznych usług, minimalizując ryzyko błędów.