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 JSONEn TypeScriptPourquoi
Une clé parfois absentecoupon?: stringOptionnelle : la propriété peut ne pas exister
Une valeur parfois nullshipped_at: string | nullNullable : la propriété existe mais peut être vide
Les deuxnote?: string | nullL'API peut l'omettre ou envoyer null
Une datestringLe JSON n'a pas de type date ; convertissez avec new Date() là où c'est nécessaire
Un grand identifiantstring si l'API l'envoie entre guillemetsLes nombres au-delà de 2^53 perdent en précision en JavaScript
Un tableau vide dans l'exempleunknown[]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ù.

Questions Fréquentes

Comment générer des types TypeScript à partir de JSON ?

Collez un vrai exemple du JSON dans un convertisseur JSON vers TypeScript. Il crée une interface pour chaque objet, avec les champs optionnels et nullables détectés d'après l'exemple.

Pourquoi res.json() est-il typé any ?

Parce que TypeScript ne peut pas savoir à la compilation ce qu'un serveur va envoyer. C'est à vous de donner un type au résultat, avec une fonction générique ou un validateur.

Un type TypeScript valide-t-il les données ?

Non. Les types disparaissent à la compilation. Pour vérifier les données à l'exécution, utilisez un validateur JSON Schema ou une bibliothèque comme Zod.

Faut-il utiliser optionnel ou null ?

Calquez-vous sur l'API : utilisez un point d'interrogation quand la clé peut manquer, et une union avec null quand la clé est présente avec la valeur null.

Comment typer les dates dans du JSON ?

En string, puisque le JSON n'a pas de type date. Convertissez-les en objets Date là où vous avez besoin de calculs sur les dates.