Comment typer une réponse d'API en TypeScript
fetch vous renvoie des données typées any, ce qui désactive TypeScript précisément là où naissent les bugs. Voici comment donner un vrai type aux données d'une API, rapidement et sans risque.
TypeScript vérifie votre code, mais il ne peut pas vérifier des données qui arrivent à l'exécution. Dès que vous appelez await response.json(), vous obtenez une valeur de type any, et à partir de là le compilateur vous laisse lire user.adress.city avec une faute de frappe sans la moindre plainte. Typer les réponses d'API comble ce trou. Ce guide montre comment écrire rapidement les types à partir d'un exemple, comment modéliser les cas délicats, et quand un type seul ne suffit pas.
Étape 1 : obtenir les types à partir d'un vrai exemple
Écrire à la main les interfaces d'une grosse réponse est lent et source d'erreurs. Copiez plutôt une vraie réponse, par exemple depuis l'onglet Réseau du navigateur ou depuis la documentation de l'API, et collez-la dans JSON vers TypeScript. Une réponse comme celle-ci :
{
"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" }
]
}
devient, avec le champ Nom racine réglé sur User :
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[];
}
Regardez ce que le générateur a déduit des deux commandes : shipped_at valait null dans l'un des éléments, il devient donc string | null ; coupon manquait dans l'un des éléments, il devient donc optionnel. Les objets imbriqués sont devenus des interfaces nommées à part entière, et les éléments de orders sont devenus Order. Tout s'exécute dans le navigateur : une réponse qui contient de vraies données clients n'est envoyée nulle part.
Étape 2 : utiliser le type avec fetch
Le moyen le plus simple de relier le type aux données est une petite fonction utilitaire :
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
Le as T est une promesse que vous faites au compilateur, pas une vérification. Si le serveur envoie autre chose, TypeScript ne s'en apercevra pas. Pour des API internes que vous maîtrisez, ce compromis est souvent acceptable ; pour tout le reste, passez à l'étape 4.
Étape 3 : modéliser les cas délicats
| Dans le JSON | En TypeScript | Pourquoi |
|---|---|---|
| Une clé parfois absente | coupon?: string | Optionnelle : la propriété peut ne pas exister |
| Une valeur parfois null | shipped_at: string | null | Nullable : la propriété existe mais peut être vide |
| Les deux | note?: string | null | L'API peut l'omettre ou envoyer null |
| Une date | string | Le JSON n'a pas de type date ; convertissez avec new Date() là où c'est nécessaire |
| Un grand identifiant | string si l'API l'envoie entre guillemets | Les nombres au-delà de 2^53 perdent en précision en JavaScript |
| Un tableau vide dans l'exemple | unknown[] | L'exemple ne montre pas à quoi ressemblent les éléments |
Préférez unknown à any pour tout ce que l'exemple ne peut pas vous dire : unknown impose une vérification avant usage, alors que any coupe la vérification sans bruit. Un type généré n'est jamais plus complet que son exemple : utilisez donc une réponse qui contient les champs optionnels, ou un tableau avec plusieurs éléments variés.
Étape 4 : valider à l'exécution quand les données ne sont pas les vôtres
Les API tierces évoluent, les proxys renvoient des pages d'erreur en HTML, et d'anciens clients rencontrent de nouveaux serveurs. Une vérification à l'exécution intercepte ces cas à la frontière, avec une erreur claire, au lieu de les laisser éclater au fin fond de l'interface. Deux approches courantes :
- JSON Schema : générez un schéma à partir du même exemple avec le générateur de JSON Schema et validez avec une bibliothèque comme Ajv. Le schéma ne dépend d'aucun langage : un backend écrit dans un autre langage peut utiliser le même fichier.
- Un validateur pensé pour TypeScript : des bibliothèques comme Zod vous permettent de décrire la forme une seule fois et d'en déduire le type TypeScript, si bien que le type et la vérification ne peuvent jamais diverger.
Dans les deux cas, validez une seule fois, à l'endroit où les données entrent dans votre programme, et laissez le reste du code s'appuyer sur le type.
Quand l'API change
Des types générés une fois pour toutes s'écartent peu à peu de l'API à mesure qu'elle grandit. Conservez dans le dépôt, à côté des types, l'exemple qui a servi à les générer : la personne suivante pourra les régénérer et voir ce qui a changé dans un diff. Si l'API publie une description OpenAPI, générer les types à partir de celle-ci est plus fiable qu'à partir d'exemples, car elle liste chaque champ optionnel et chaque valeur possible, y compris ceux qu'une réponse isolée ne montre jamais. Pour les API qui n'en ont pas, un validateur à l'exécution comme à l'étape 4 transforme une incohérence silencieuse en une erreur visible dans les journaux.
interface ou type ?
Pour des données d'API, les deux fonctionnent. Les interfaces peuvent être étendues et sont fusionnées quand elles sont déclarées deux fois, ce que certaines équipes apprécient pour les types publics ; les alias de type peuvent en plus exprimer des unions et des types mappés. Choisissez un style pour le projet et tenez-vous-y. Le générateur produit l'un comme l'autre.
D'autres langages à partir du même exemple
Le même exemple JSON peut aussi devenir des structs Go, des modèles Pydantic ou des classes C# dans JSON vers types, ce qui aide à garder un frontend et un backend alignés sur la même structure. Si l'exemple lui-même est cassé, le formateur JSON vous montre où.