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:
- Zmiana struktury na backendzie:
full_name→fullNameprowadzi doundefined. - Niezgodność typów: ciąg
"123"zamiastnumber123 powoduje konkatenację zamiast dodawania. - Pola warunkowe:
addressbrakuje w przypadkach brzegowych, awaria nauser.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.
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.
— Editorial Team
Brak komentarzy.