Tutoriel
Générer docs d’API JSON, types TypeScript et client API depuis OpenAPI : du Schema au codegen
Docs à la main, types à la main, fetch à la main qui dérivent chacun coûtent plus cher que l’endpoint. Traitez le Schema OpenAPI comme contrat : docs, types et client poussent du même JSON.
Le coût de GET /invoices n’est rarement le handler. Ce sont trois artefacts qui périment chacun : docs humaines, types TypeScript pour le compilateur, wrapper fetch pour le runtime. On colle encore du JSON d’exemple dans un wiki, on redéclare les champs dans types.ts, on concatène des URL dans api.ts. Un Schema OpenAPI plie cela en un contrat lisible par une machine. Vous maintenez openapi.json (ou YAML) ; le site de docs, le fichier de types et le client sont générés. Un champ change, les trois bougent. MCP et JSON Schema traite les entrées d’outil ; la sortie structurée traite la réponse finale du modèle. Ici seulement l’API JSON HTTP : Schema vers docs, types et client. Spec : OpenAPI 3.1. Générateur par défaut : openapi-typescript.
Un Schema, trois produits : docs, types, client
Fixez l’objectif. Les docs OpenAPI ne sont pas un autre livre Markdown ; elles rendent la même spec. Scalar, Swagger UI, Redoc sont des lecteurs. Les types TypeScript ne sont pas une seconde interface à la main ; ils projettent paths et components.schemas. Un client API n’est pas « encore un wrapper axios » ; ce sont des fonctions nées de operationId ou des chemins, avec types de requête et de réponse. Si deux des trois sont manuscrits, le Schema est une affiche.
Choisissez les outils plus tard ; ayez un contrat d’abord. Exportez une spec depuis FastAPI, Nest, Spring ou Go, ou écrivez openapi.json puis implémentez. Interdit : écrire le client d’abord et inventer les docs d’après le « ressenti » des appelants. Pas de source unique, et la CI ne peut départager. Si un agent use REST comme outil, il doit lire cet OpenAPI, pas un second inputSchema MCP — deux contrats dérivent tout de suite. Couches : l’article MCP ci-dessus.
OpenAPI 3.1 aligne components.schemas sur JSON Schema 2020-12 : le nullable est type: ["string", "null"], plus le nullable: true de 3.0. Moins de « docs optionnelles, types requis ». L’objet vu dans JSONVue doit être celui que le générateur mange — pas un dialecte traduit à l’export.
| Produit | D’où il vient | Ne pas écrire à la main |
|---|---|---|
| Docs JSON API | Le même JSON OpenAPI, rendu par Scalar / Swagger UI | JSON d’exemple et captures dans un wiki |
| Types TypeScript | openapi-typescript émet paths / components | Un interface Invoice parallèle à la spec |
| Client API / hooks | openapi-fetch, Orval ou hey-api émettent les fonctions d’appel | URL collées et as Invoice |
Écrire d’abord le JSON OpenAPI : paths, components, operationId
Un générateur ne dépasse pas la qualité de la spec. Minimum : version openapi, info, paths, modèles partagés dans components.schemas. Donnez à chaque opération un operationId stable (listInvoices, createInvoice) ; le client s’en sert comme nom de fonction. Un chemin change : ne faites pas flotter l’id. Déclarez application/json sur requête et succès, et $ref les composants au lieu de coller la table de champs sur chaque path.
Les erreurs font partie du contrat. Donnez des JSON Schema à 400 / 401 / 404 / 422 pour que le client forme une union lisible, pas un unknown. Mettez query et path dans parameters ; ne les cachez pas dans le body avec un accord oral dans les docs. Mettez Bearer / OIDC dans securitySchemes pour que le client généré sache où va le jeton. Le protocole d’auth : OAuth 2.1 — en REST aussi, le jeton est dans l’en-tête, pas dans l’enveloppe JSON.
Ci-dessous une API factures minimale. Ce n’est pas une spec produit, mais elle suffit à faire tourner docs, types et client. Invoice n’apparaît qu’une fois ; list et create la référencent. Formatez-la en local pour confirmer le parse, puis vérifiez que les $ref tiennent.
{
"openapi": "3.1.1",
"info": {
"title": "Invoices API",
"version": "2026.09.18"
},
"paths": {
"/invoices": {
"get": {
"operationId": "listInvoices",
"parameters": [
{
"name": "status",
"in": "query",
"schema": {
"type": "string",
"enum": ["draft", "paid", "void"]
}
}
],
"responses": {
"200": {
"description": "Invoice list",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["items"],
"properties": {
"items": {
"type": "array",
"items": { "$ref": "#/components/schemas/Invoice" }
}
}
}
}
}
}
}
},
"post": {
"operationId": "createInvoice",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/InvoiceDraft" }
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Invoice" }
}
}
}
}
}
}
},
"components": {
"schemas": {
"InvoiceDraft": {
"type": "object",
"required": ["customerId", "amount"],
"properties": {
"customerId": { "type": "string" },
"amount": { "type": "integer", "minimum": 1 },
"note": { "type": ["string", "null"] }
}
},
"Invoice": {
"allOf": [
{ "$ref": "#/components/schemas/InvoiceDraft" },
{
"type": "object",
"required": ["id", "status"],
"properties": {
"id": { "type": "string" },
"status": {
"type": "string",
"enum": ["draft", "paid", "void"]
}
}
}
]
}
}
}
}
Générer des docs JSON API lisibles
Le bon site de docs copie openapi.json dans un dossier statique au build et accroche un lecteur à côté. Scalar, Swagger UI, Redoc lisent le même fichier. Ne traitez pas « docs » comme une autre pile Markdown à recroiser avec la spec en CI. Les humains éditent les docs, les machines la spec : elles forkent le lendemain.
Un lecteur ne montre que ce que la spec contient : pas d’example, pas d’échantillon à copier ; pas d’erreurs, pas d’histoire d’échec ; description vide, il reste un chemin. Finissez le contrat avant de générer les docs. Prévisualisez le lecteur dans le navigateur ; inspectez la spec dans JSONVue — plus vite que chasser un $ref cassé dans un énorme YAML.
Mettez l’URL des docs dans info.contact ou un portail interne, mais ne tenez pas une seconde « table de champs pour le produit ». Les enums et required que le produit veut sont déjà dans enum / required du Schema. Deux tables de champs sont la porte classique de la dérive.
Générer les types TypeScript : openapi-typescript
openapi-typescript est un bon défaut : zéro runtime, OpenAPI 3.0 et 3.1, paths et components. Une commande : npx openapi-typescript openapi.json -o src/api/schema.d.ts. Une spec distante marche ; la CI de prod doit clouer un fichier du dépôt pour ne pas frapper une URL qui bouge.
import type { paths, components } from "./schema";
type Invoice = components["schemas"]["Invoice"];
type ListOk =
paths["/invoices"]["get"]["responses"][200]["content"]["application/json"];
export type { Invoice, ListOk };
Indexez par chemin ; ne déclarez pas une autre interface. paths["/invoices"]["get"]["responses"][200]["content"]["application/json"] est le corps succès de la liste. Réutilisez via components["schemas"]["Invoice"]. Un chemin change : le compilateur casse aux appels, au lieu de vous faire chercher trois Invoice.
Les types ne sont pas une validation runtime. Le .d.ts généré n’existe pas dans le navigateur. Le fil peut encore livrer un JSON incomplet ; un compile vert dit seulement que vous avez écrit le client contre le contrat. Pour une porte runtime, générez Zod avec Orval/Kubb, ou validez les réponses critiques. Ne faites pas de « TypeScript vert » le seul QA.
Générer un client API : openapi-fetch, Orval, hey-api
S’il ne faut que types et appels, couplez openapi-fetch : createClient<paths>({ baseUrl }) puis client.GET("/invoices", { params: { query } }). Corps, query et path sont typés ; vous n’envoyez pas un body createInvoice à list. C’est le défaut fin 2026 : peu de fichiers, votre fetch, pas d’entrepôt généré.
import createClient from "openapi-fetch";
import type { paths } from "./schema";
const client = createClient({
baseUrl: "https://api.example.com",
headers: { Authorization: "Bearer …" }
});
const { data, error } = await client.GET("/invoices", {
params: { query: { status: "paid" } }
});
const created = await client.POST("/invoices", {
body: { customerId: "cus_1", amount: 1999, note: null }
});
Hooks React Query, Zod, mocks MSW : Orval. Il pousse fonctions et hooks depuis la même spec — pour les apps qui ont déjà Query comme couche données. hey-api (@hey-api/openapi-ts) : fonctions SDK et intercepteurs quand vous voulez operationId comme nom de méthode sans des milliers de fichiers. Le template typescript-axios d’OpenAPI Generator marche encore ; la sortie est plus lourde, un projet neuf n’a pas à commencer là.
Sur chaque pipeline, n’éditez pas les fichiers générés. Changez le comportement dans la spec, la config du générateur, ou une couche à la main (délais, retries, en-tête locataire). Éditer le généré abandonne le contrat. Jeton sur Authorization, pas dans le body JSON.
| Outil | Ce que vous obtenez | Quand le choisir |
|---|---|---|
| openapi-typescript + openapi-fetch | Types + client fetch mince, quasi zéro runtime | Départ par défaut ; vous tenez le cache |
| Orval | Types + fonctions + hooks Query + Zod/MSW optionnels | Vous avez déjà TanStack Query ; les mocks doivent suivre la spec |
| hey-api / Kubb | Noms de méthodes SDK, ou multi-sorties par plugins | Plateforme d’intercepteurs, ou un fichier par opération |
Couper la dérive en CI ; vérifier le contrat dans JSONVue
Le contrat change : régénérez. En CI : linter la spec (Spectral ou Redocly) → lancer le générateur → git diff --exit-code sur l’arbre généré. Échec s’il y a un diff, pour forcer les nouveaux types. N’exécutez pas le codegen une fois sur un portable. Le backend a changé un champ, les types front datent de la semaine dernière — accident standard.
npx --yes openapi-typescript openapi.json -o src/api/schema.d.ts
npx --yes @redocly/cli lint openapi.json
git diff --exit-code -- src/api/schema.d.ts
Un labo produit au moins trois JSON : la spec OpenAPI, un body 200, un body d’erreur 422, plus une fixture mince pour le générateur. Métiers différents ; ne les mélangez pas en une « validation générique ». Gardez un échec de chaque : $ref cassé, operationId manquant, content sans application/json, deux specs qui n’ont changé qu’un enum.
Vous pouvez finir dans le navigateur :Formateur JSON pour voir si la spec parse ;Validateur JSON Schema pour components.schemas contre un vrai body ;JSON Diff pour comparer deux OpenAPI ou arguments modèle vs body aval. Les données restent ici. Pour aller plus loin : MCP et JSON Schema, sortie structurée, et générer des coques d’outil depuis la même table de champs.
Liés : MCP et JSON Schema, sortie structurée IA, erreurs JSON générées par l’IA, qu’est-ce que MCP.
FAQ
YAML ou JSON pour la spec ?
Les générateurs mangent les deux. YAML diffère plus gentiment pour les humains ; envoyez du JSON au navigateur et à JSONVue. Clouez une source en CI ; l’autre n’est qu’un artefact de build, pour que deux sources ne s’éditent pas.
Puis-je ne générer que les types, pas le client ?
Oui, et souvent il le faut. openapi-typescript n’émet que du .d.ts. Un wrapper axios existant peut commencer à manger ces types. Quand chemins et bodies sont clôturés par les types, passez à openapi-fetch ou Orval. Plus contrôlable que trois mille fichiers le premier jour.
OpenAPI 3.0 ou 3.1 pour un projet neuf ?
Nouveaux contrats : 3.1, pour JSON Schema 2020-12 et moins de dialecte sur null et const. N’upgradez pas de force un parc 3.0 ; confirmez que le générateur (openapi-typescript 7.x, Orval 8) documente 3.1, puis migrez. Passez Swagger 2.0 en 3.x avant de parler client.
OpenAPI et MCP inputSchema peuvent-ils partager un document ?
Les champs métier peuvent partager un JSON Schema canonique, puis générer un requestBody OpenAPI et un inputSchema MCP. Ne donnez pas tout un fichier OpenAPI à un client MCP comme catalogue d’outils — transport, auth et noms de méthodes sont d’autres couches. Une table de champs, deux coques, dérive moins que le copier-coller.
Résumé et suite
Le codegen OpenAPI tient en une phrase : un Schema lisible par machine pousse des docs JSON API humaines, des types TypeScript à la compilation, et un client typé. Écrire l’un des trois à la main, et le contrat n’en est plus un.
Livrez dans cet ordre : openapi.json parse, a des operationId, liste les erreurs ; accrochez un lecteur ; générez les types ; puis choisissez un client. Gardez spec, body succès et body échec dans JSONVue sur cette machine. Entrées d’outil : MCP et JSON Schema. Réponses modèle : sortie structurée.