Runtime-Validierung von API-Antworten: Warum TypeScript nicht reicht
TypeScript prüft Typen nur zur Kompilierzeit. Daten aus APIs kommen zur Laufzeit an – ohne Garantie, dass sie zu euren Interfaces passen. Ohne zusätzliche Validierung führen Abweichungen zu Fehlern, die Nutzer sehen: undefinierte Felder, falsche Typen, fehlende Objekte.
Probleme ohne Laufzeitprüfungen
TypeScript-Interfaces vermitteln ein falsches Sicherheitsgefühl. Hier typischer Code ohne Validierung:
interface User {
id: number;
name: string;
email: string;
}
const getUser = async (): Promise<User> => {
const res = await fetch('/api/user');
return res.json(); // Keine Prüfung der echten Daten
};
TypeScript vertraut auf die Typannotation und ignoriert die echte Server-Antwort. Wichtige Risiken:
- Backend-Änderungen:
full_name→fullNameführt zuundefined. - Typkonflikte: String
"123"statt Number123verursacht Verkettung statt Addition. - Bedingte Felder:
addressfehlt in Randfällen, Absturz beiuser.address.city. - Third-Party-APIs mit veralteter Dokumentation.
Schema-Validierungs-Bibliotheken
Die Lösung? Bibliotheken für Laufzeit-Schema-Validierung. Sie parsen echte Daten und werfen Fehler bei Abweichungen. Top-Optionen für TypeScript:
- Zod: Leitet Typen aus Schemas ab, riesiges Ökosystem.
- Yup: Passt super zu Formik.
- Valibot: Bundle-freundlich, tree-shakable.
- Joi: Node.js-Wurzeln, backend-orientiert.
- ArkType: Performance-fokussiert.
Umsetzung mit Zod
Zod vereint Schema, Typen und Validierung in einem. User-Beispiel:
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); // Fehler bei Abweichung
};
parse() wirft einen ZodError mit Details bei Fehlern. Nutzt safeParse() für ein Objekt mit success und error.
Skalierung in echten Projekten
Bei großen Apps zentralisiert Schemas für alle Endpoints. Fetch so wrappen:
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));
Fehlerbehandlung: An Sentry loggen, nutzerfreundliche Meldungen zeigen. Bei Arrays jedes Element validieren.
z.array(UserSchema)für Listen.z.union()für polymorphe Antworten.z.lazy()für rekursive Strukturen.z.transform()zum Normalisieren von Daten.
Wichtige Erkenntnisse
- TypeScript garantiert keine Laufzeit-Typen für externe Daten: APIs, localStorage, URL-Parameter.
- Schema-Validierung fängt Abweichungen früh ab, spart Debug-Zeit.
- Zod ist Frontend-Standard: Typen aus Schemas, einfache Integration.
- In Teams mit separatem Backend verhindert es stille Fehler.
- Mit Fetch-Wrappers skalieren für Konsistenz.
Runtime-Validierung schließt die Lücke zwischen TypeScripts Kompilierzeit-Prüfungen und Ausführung. In der Produktion reduziert sie Bugs durch asynchrone API-Verträge massiv.
— Editorial Team
Noch keine Kommentare.