Powrót do strony głównej

Walidacja API w runtime z Zod i TypeScript

Artykuł wyjaśnia ograniczenia TypeScript w runtime dla danych API i proponuje schema validation z Zod. Podane są przykłady kodu i porównanie bibliotek dla deweloperów middle/senior.

Zod do runtime-walidacji TypeScript API
Advertisement 728x90

Walidacja runtime odpowiedzi API: dlaczego TypeScript to za mało

TypeScript zapewnia sprawdzanie typów tylko na etapie kompilacji. W runtime dane z API przychodzą bez gwarancji zgodności z interfejsami. Bez dodatkowej walidacji niezgodności objawiają się w postaci błędów użytkowników: niezdefiniowane pola, nieprawidłowe typy, brakujące obiekty.

Problemy bez walidacji runtime

Interfejsy TypeScript tworzą fałszywe poczucie kontroli. Przykład typowego kodu bez walidacji:

interface User {
  id: number;
  name: string;
  email: string;
}

const getUser = async (): Promise<User> => {
  const res = await fetch('/api/user');
  return res.json(); // Brak sprawdzenia rzeczywistych danych
};

TypeScript ufa adnotacjom, ignorując faktyczną odpowiedź serwera. Główne ryzyka:

Google AdInline article slot
  • Zmiana struktury na backendzie: full_namefullName prowadzi do undefined.
  • Niezgodność typów: ciąg "123" zamiast number 123 powoduje konkatenację zamiast dodawania.
  • Pola warunkowe: address brakuje w przypadkach brzegowych, awaria na user.address.city.
  • Zewnętrzne API z przestarzałą dokumentacją.

Biblioteki do walidacji schematów

Rozwiązanie — biblioteki do walidacji schematów w runtime. Parsują one rzeczywiste dane i zgłaszają błędy w przypadku niezgodności. Popularne opcje w TypeScript:

  • Zod: wyprowadza typy ze schematów, szeroka ekosystem.
  • Yup: integracja z Formik.
  • Valibot: optymalizacja pakietu, tree-shaking.
  • Joi: z Node.js, zorientowany na backend.
  • ArkType: skupienie na wydajności.

Implementacja z Zod

Zod łączy schemat, typy i walidację. Przykład dla User:

import { z } from 'zod';

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
});

type User = z.infer<typeof UserSchema>;

const getUser = async (): Promise<User> => {
  const res = await fetch('/api/user');
  const data = await res.json();
  return UserSchema.parse(data); // Błąd w przypadku niezgodności
};

Metoda parse() rzuca ZodError ze szczegółami w przypadku niepowodzenia. Alternatywa safeParse() zwraca obiekt z success i error.

Google AdInline article slot

Skalowanie w projektach

W dużych aplikacjach tworzą zcentralizowane schematy dla wszystkich endpointów. Zintegruj z wrapperami fetch:

const apiFetch = async <T>(url: string, schema: z.ZodSchema<T>): Promise<T> => {
  const res = await fetch(url);
  const data = await res.json();
  return schema.parse(data);
};

const users = await apiFetch('/api/users', z.array(UserSchema));

Obsługa błędów: loguj w Sentry, pokazuj przyjazne użytkownikowi komunikaty. Dla tablic sprawdzaj każdy element.

  • Używaj z.array(UserSchema) dla list.
  • z.union() dla odpowiedzi polimorficznych.
  • z.lazy() dla struktur rekurencyjnych.
  • z.transform() do normalizacji danych.

Co jest ważne

  • TypeScript nie gwarantuje typów w runtime dla danych zewnętrznych: API, localStorage, parametry URL.
  • Walidacja schematów wykrywa niezgodności natychmiast, redukując czas debugowania.
  • Zod — standard dla frontendu: typy ze schematów, prosta integracja.
  • W zespołach z niezależnym backendem walidacja zapobiega cichym awariom.
  • Skaluj przez wrappery fetch dla jednolitości.

Walidacja runtime uzupełnia TypeScript, zamykając lukę między kompilacją a wykonaniem. W produkcji minimalizuje to błędy wynikające z niesynchronizowanych kontraktów API.

Google AdInline article slot

— Editorial Team

Advertisement 728x90

Czytaj dalej