API-Antworten in TypeScript typisieren
fetch liefert Daten mit dem Typ any, und damit schaltet sich TypeScript genau dort ab, wo Fehler entstehen. So geben Sie API-Daten schnell und sicher einen echten Typ.
TypeScript prüft Ihren Code, aber keine Daten, die erst zur Laufzeit ankommen. Sobald Sie await response.json() aufrufen, erhalten Sie einen Wert vom Typ any, und von da an lässt Sie der Compiler user.adress.city mit Tippfehler lesen, ohne sich zu beschweren. Wer API-Antworten typisiert, schließt diese Lücke. Dieser Ratgeber zeigt, wie Sie die Typen schnell aus einem Beispiel gewinnen, wie Sie die kniffligen Stellen abbilden und wann ein Typ allein nicht genügt.
Schritt 1: die Typen aus einem echten Beispiel gewinnen
Interfaces für eine große Antwort von Hand zu schreiben ist langsam und fehleranfällig. Kopieren Sie stattdessen eine echte Antwort, etwa aus dem Tab Netzwerk der Entwicklertools im Browser oder aus der API-Dokumentation, und fügen Sie sie in JSON to TypeScript ein. Eine Antwort wie diese:
{
"id": 1024,
"name": "Ada",
"tags": ["admin", "beta"],
"address": { "city": "London", "zip": "N1 9GU" },
"orders": [
{ "order_id": "A-17", "total": 42, "shipped_at": "2026-09-01T10:00:00Z" },
{ "order_id": "A-18", "total": 18.5, "shipped_at": null, "coupon": "WELCOME" }
]
}
wird, wenn Name des Wurzeltyps auf User steht, zu:
export interface Address {
city: string;
zip: string;
}
export interface Order {
order_id: string;
total: number;
shipped_at: string | null;
coupon?: string;
}
export interface User {
id: number;
name: string;
tags: string[];
address: Address;
orders: Order[];
}
Achten Sie darauf, was der Generator aus den beiden Bestellungen abgeleitet hat: shipped_at war in einem Eintrag null, deshalb ist es string | null; coupon fehlte in einem Eintrag, deshalb ist es optional. Verschachtelte Objekte wurden zu eigenen, benannten Interfaces, und die Einträge von orders wurden zu Order. Alles läuft im Browser, eine Antwort mit echten Kundendaten wird also nirgendwohin gesendet.
Schritt 2: den Typ mit fetch verwenden
Am einfachsten verbinden Sie den Typ mit den Daten über eine kleine Hilfsfunktion:
async function getJson<T>(url: string): Promise<T> {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return (await res.json()) as T;
}
const user = await getJson<User>('/api/users/1024');
user.address.city; // string, checked by the compiler
Das as T ist ein Versprechen, das Sie dem Compiler geben, keine Prüfung. Schickt der Server etwas anderes, bemerkt TypeScript das nicht. Für interne APIs, die Sie selbst kontrollieren, ist dieser Kompromiss oft in Ordnung; für alles andere lesen Sie weiter bei Schritt 4.
Schritt 3: die kniffligen Stellen abbilden
| Im JSON | In TypeScript | Warum |
|---|---|---|
| Ein Schlüssel, der manchmal fehlt | coupon?: string | Optional: Die Eigenschaft existiert unter Umständen nicht |
| Ein Wert, der manchmal null ist | shipped_at: string | null | Nullable: Die Eigenschaft existiert, kann aber leer sein |
| Beides | note?: string | null | Die API lässt sie womöglich weg oder schickt null |
| Ein Datum | string | JSON hat keinen Datumstyp; bei Bedarf mit new Date() umwandeln |
| Eine große ID | string, wenn die API sie in Anführungszeichen schickt | Zahlen über 2^53 verlieren in JavaScript an Genauigkeit |
| Ein leeres Array im Beispiel | unknown[] | Das Beispiel zeigt nicht, wie die Einträge aussehen |
Ziehen Sie unknown dem Typ any für alles vor, was das Beispiel nicht verrät: unknown erzwingt eine Prüfung vor der Verwendung, während any die Prüfung stillschweigend abschaltet. Ein erzeugter Typ ist nur so vollständig wie sein Beispiel. Verwenden Sie deshalb eine Antwort, die auch die optionalen Felder enthält, oder ein Array mit mehreren unterschiedlichen Einträgen.
Schritt 4: zur Laufzeit prüfen, wenn die Daten nicht von Ihnen stammen
APIs von Drittanbietern ändern sich, Proxys liefern HTML-Fehlerseiten zurück, und alte Clients treffen auf neue Server. Eine Prüfung zur Laufzeit fängt solche Fälle an der Grenze ab, mit einer klaren Fehlermeldung, statt irgendwo tief in der Benutzeroberfläche. Zwei verbreitete Ansätze:
- JSON Schema: Erzeugen Sie aus demselben Beispiel mit dem JSON Schema Generator ein Schema und prüfen Sie mit einer Bibliothek wie Ajv. Das Schema ist sprachunabhängig, ein Backend in einer anderen Sprache kann also dieselbe Datei verwenden.
- Ein TypeScript-zentrierter Validator: Mit Bibliotheken wie Zod beschreiben Sie die Struktur einmal und leiten den TypeScript-Typ daraus ab, sodass Typ und Prüfung nie auseinanderlaufen können.
In beiden Fällen gilt: einmal prüfen, dort, wo die Daten in Ihr Programm kommen, und der übrige Code verlässt sich auf den Typ.
Wenn sich die API ändert
Einmal erzeugte Typen veralten, während die API wächst. Legen Sie das Beispiel, aus dem Sie die Typen erzeugt haben, neben den Typen im Repository ab, damit die nächste Person sie neu erzeugen und in einem Diff sehen kann, was sich geändert hat. Veröffentlicht die API eine OpenAPI-Beschreibung, ist es zuverlässiger, die Typen daraus zu erzeugen als aus Beispielen, weil sie jedes optionale Feld und jeden möglichen Wert auflistet, auch die, die eine einzelne Antwort nie zeigt. Bei APIs ohne eine solche Beschreibung macht ein Laufzeit-Validator aus Schritt 4 aus einer stillen Abweichung einen Fehler, den Sie in den Logs sehen.
interface oder type?
Für API-Daten funktioniert beides. Interfaces lassen sich erweitern und werden zusammengeführt, wenn sie zweimal deklariert werden, was manche Teams für öffentliche Typen schätzen; Typ-Aliase können zusätzlich Unions und Mapped Types ausdrücken. Entscheiden Sie sich im Projekt für einen Stil und bleiben Sie dabei. Der Generator erzeugt beides.
Andere Sprachen aus demselben Beispiel
Dasselbe JSON-Beispiel kann in JSON zu Typen auch zu Go-Structs, Pydantic-Modellen oder C#-Klassen werden; so bleiben Frontend und Backend bei derselben Struktur. Ist das Beispiel selbst fehlerhaft, zeigt der JSON Formatter, wo.