So tippen Sie API-Antworten in TypeScript
Jeder Frontend-Entwickler kennt das: Sie rufen Daten von einer API ab, greifen auf eine Eigenschaft zu und erhalten zur Laufzeit undefined – obwohl TypeScript sich nie beschwert hat. Das Problem liegt nicht an Ihrem Code. Es liegt daran, dass das Typsystem von TypeScript nur zur Kompilierzeit arbeitet. Es hat keine Ahnung, was Ihre API tatsächlich zurückgibt.
Dieser Artikel behandelt praktische Muster für das Typisieren von API-Antworten in TypeScript, von der Definition grundlegender Interfaces bis hin zur Laufzeitvalidierung und vertragsgesteuerter Typgenerierung.
Wichtigste Erkenntnisse
- Das Typsystem von TypeScript arbeitet nur zur Kompilierzeit – es kann die tatsächliche Form der von einer API zur Laufzeit zurückgegebenen Daten nicht validieren.
- Verwenden Sie Interfaces oder Type-Aliase mit diskriminierten Unions, um klare, erwartete Antwortformen zu definieren.
- Ein generischer Fetch-Wrapper hält die Aufrufstellen sauber und macht erwartete Typen explizit.
- Verwenden Sie für nicht vertrauenswürdige oder externe Daten Laufzeitvalidierungsbibliotheken wie Zod, Valibot oder ArkType, um die Lücke zwischen Kompilierzeittypen und realen Antworten zu schließen.
- Wenn eine OpenAPI-Spezifikation existiert, generieren Sie Ihre Typen daraus, um Frontend- und Backend-Verträge synchron zu halten.
Warum TypeScript Sie zur Laufzeit nicht schützen kann
Wenn Sie response.json() aufrufen, behandelt TypeScript das Ergebnis typischerweise als unknown (oder manchmal any, abhängig von den Umgebungstypisierungen). Das bedeutet, Sie können es zu allem casten, was Sie wollen – und der Compiler wird Ihnen vollständig vertrauen.
// ❌ TypeScript vertraut diesem Cast blind
const data = (await response.json()) as User
console.log(data.email) // Könnte zur Laufzeit undefined sein
Dies ist die zentrale Grenze, die Sie verstehen müssen: TypeScript-typisierte API-Antworten geben Ihnen Sicherheit zur Kompilierzeit, aber sie validieren nicht das tatsächliche JSON, das Ihre API zurückgibt. Wenn sich die Form ändert, wird TypeScript es nicht wissen.
Typen für erwartete Antwortformen definieren
Der erste Schritt besteht darin, dem, was Sie erwarten, Struktur zu geben. Verwenden Sie Interfaces oder Type-Aliase, um die Antwortform zu beschreiben:
interface User {
id: string
name: string
email: string
}
Für konsistente APIs behandelt ein generischer Wrapper sowohl Erfolgs- als auch Fehlerzustände sauber:
type ApiResponse<T> =
| { status: "ok"; data: T }
| { status: "error"; message: string }
Dieses diskriminierte Union-Muster ist transparenter als ein einzelner Typ mit vielen optionalen Feldern. Wenn Sie status prüfen, grenzt TypeScript den Typ automatisch ein – keine zusätzlichen Guards erforderlich.
Fetch-Antworten mit einem generischen Wrapper typisieren
Das Typisieren von Fetch-Antworten in TypeScript ist mit einem wiederverwendbaren Helfer sauberer:
async function apiFetch<T>(url: string): Promise<T> {
const response = await fetch(url)
if (!response.ok) throw new Error(`HTTP error: ${response.status}`)
return response.json() as Promise<T>
}
// Verwendung
const user = await apiFetch<User>("/api/users/1")
Dies hält Ihre Aufrufstellen sauber und macht die erwartete Form explizit. Das gleiche Muster funktioniert mit Axios oder jedem anderen HTTP-Client.
Discover how at OpenReplay.com.
Laufzeitvalidierung: Die Lücke schließen
Type-Assertions wie as User sind ein Versprechen an den Compiler, keine Garantie. Für nicht vertrauenswürdige externe Daten – Drittanbieter-APIs, benutzergenerierte Inhalte oder jede Antwort, die Sie nicht vollständig kontrollieren – benötigen Sie Laufzeitvalidierung.
Zod ist die am weitesten verbreitete Option. Sie definieren ein Schema, parsen die Antwort und erhalten ein vollständig typisiertes Ergebnis:
import { z } from "zod"
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
})
type User = z.infer<typeof UserSchema> // Vom Schema abgeleitet
const data = await response.json()
const user = UserSchema.parse(data) // Wirft einen Fehler, wenn die Form falsch ist
Der Hauptvorteil: Ihre TypeScript-Typen und Ihre Laufzeitvalidierung bleiben synchron, weil sie von derselben Quelle der Wahrheit abgeleitet werden.
Erwähnenswerte Alternativen:
Verwenden Sie .parse(), wenn Sie bei ungültigen Daten einen Fehler werfen möchten, oder .safeParse(), wenn Sie Fehler elegant ohne Exceptions behandeln möchten.
Vertragsgesteuerte Typisierung mit OpenAPI
Wenn Ihre API eine OpenAPI-Spezifikation hat, können Sie TypeScript-Typen automatisch mit openapi-typescript generieren:
npx openapi-typescript ./api-spec.yaml -o ./types/api.d.ts
Dieser Ansatz hält Ihre Typen mit Ihrer API-Dokumentation synchron. Er ist komplementär zur Laufzeitvalidierung – generierte Typen handhaben die Kompilierzeitebene, während Zod oder Valibot die Laufzeitebene handhaben.
Den richtigen Ansatz wählen
| Szenario | Empfohlenes Muster |
|---|---|
| Interne API unter Ihrer Kontrolle | Interface + Type-Assertion |
| Gemeinsamer Vertrag mit Backend | OpenAPI-generierte Typen |
| Drittanbieter- oder nicht vertrauenswürdige API | Zod/Valibot-Schemavalidierung |
| Beide Sicherheitsebenen erforderlich | Generierte Typen + Laufzeitschema |
Fazit
TypeScript gibt Ihnen Struktur und Autovervollständigung, aber es kann nicht validieren, was über das Netzwerk ankommt. Für APIs, denen Sie vertrauen, reichen ein gut definiertes Interface und ein typisierter Fetch-Wrapper aus. Für externe oder unvorhersehbare Daten kombinieren Sie Ihre Typen mit einem Laufzeitvalidator wie Zod. Wenn Sie eine API-Spezifikation haben, generieren Sie Ihre Typen daraus. Diese Ansätze konkurrieren nicht – sie funktionieren am besten zusammen.
FAQs
Nein. Das Schlüsselwort 'as' ist eine Type-Assertion, keine Laufzeitprüfung. Es weist den Compiler an, einen Wert als bestimmten Typ zu behandeln, tut aber nichts, um die tatsächlichen Daten zu verifizieren. Wenn die Form der API-Antwort von dem abweicht, was Sie assertiert haben, erhalten Sie Laufzeitfehler, die TypeScript nicht abfangen kann.
Verwenden Sie Zod oder eine ähnliche Laufzeitvalidierungsbibliothek, wenn Sie Daten aus einer Quelle konsumieren, die Sie nicht vollständig kontrollieren, wie z. B. Drittanbieter-APIs oder öffentliche Endpunkte. Wenn Sie sowohl Frontend als auch Backend besitzen und großes Vertrauen in die Antwortform haben, ist ein einfaches Interface mit einer Type-Assertion oft ausreichend.
Ja, und diese Kombination wird für maximale Sicherheit empfohlen. OpenAPI-generierte Typen geben Ihnen Autovervollständigung und Typprüfung zur Kompilierzeit, während Zod-Schemas die tatsächlichen Antwortdaten zur Laufzeit validieren. Zusammen decken sie beide Ebenen des Typsicherheitsspektrums ab.
Die response.json()-Methode der Fetch-API gibt standardmäßig in TypeScript ein Promise von any zurück. Das bedeutet, der Compiler erzwingt keine Typprüfungen auf dem Ergebnis, es sei denn, Sie assertieren oder validieren es explizit. Einige Teams überschreiben dies mit strengeren Typisierungen, die stattdessen unknown zurückgeben und so explizite Validierung erzwingen.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.