On repère généralement le moment où un pipeline API commence à mentir à l'équipe. Un schéma change, les types générés se mettent à jour sans broncher, la PR passe au vert, puis quelqu'un côté front-end continue de lire l'ancienne structure de réponse parce que le wrapper a masqué l'erreur avec un cast. C'est là que réside le problème d'OpenAPI TypeScript, pas dans la capacité d'un générateur à produire des interfaces.
La question utile est plus difficile. Quel contrat voulez-vous entre le schéma, le transport et la validation, et quelles parties devraient échouer vite au build plutôt que de fuiter dans le runtime ? Une fois que vous la posez OpenAPI TypeScript comme un choix de pipeline, les compromis deviennent beaucoup plus clairs et les outils cessent de prétendre être la solution complète.
Table des matières
- Pourquoi les types générés ne sont pas synonymes d'un API sûr
- Générer des types TypeScript à partir d'une spec OpenAPI
- Choisir entre types purs, clients complets ou pas de codegen
- Implémenter un client typé léger autour de Fetch ou Axios
- Ajouter une validation au runtime avec zod, ajv ou io-ts
- Intégrer la génération, la validation et les tests de contrat dans la CI
- Pipelines maintenables, performance et checklist finale
Pourquoi les types générés ne sont pas synonymes d'un API sûr
Un collègue merge une PR qui ajoute un champ de réponse optionnel. Le fichier généré se met à jour proprement, le diff est anodin, et tout le monde passe à autre chose. Puis le front continue de lire une forme plus ancienne via un wrapper écrit à la main, « temporairement » casté avec as any, et la production se comporte comme si le contrat n’avait jamais changé.
C’est le piège des types générés. TypeScript ne peut protéger que le code qui consomme les types générés, et seulement si la couche de transport n’efface pas à nouveau le contrat. Le côté OpenAPI vous fournit un schéma, pas une garantie que chaque appelant le respecte. Le discussion autour de la compréhension des connexions API est utile ici car elle déplace la conversation d'un outil unique vers la manière dont les systèmes se connectent.
Où se cachent les erreurs
Les points de rupture les plus courants sont banals, pas exotiques. Dérive du schéma se produit lorsque la spécification OpenAPI et le service déployé cessent de correspondre. Couverture partielle apparaît lorsqu'une spécification ne modélise que le cas nominal, alors que l'application dépend de cas limites non documentés. Wrappers manuels sont souvent là où les types se fragilisent, surtout quand on veut « aller vite » et qu’on utilise any ou un cast de réponse trop lâche.
Règle pratique : si le wrapper peut mentir, le générateur ne peut pas vous sauver.
Il y a aussi une lacune au runtime. Les types TypeScript disparaissent après compilation, ils ne peuvent donc pas rejeter un JSON malformé qui transite sur le réseau. Le réseau se moque de ce que votre éditeur a inféré, et c’est pourquoi un client généré n’est qu’une couche dans un pipeline API plus sûr.
La question opérationnelle plus large, c’est la sécurité et la discipline des contrats, pas seulement le confort du développeur. Si vous voulez une vue structurée de la façon dont les contrats API s’intègrent dans le cycle de vie d’une application, ce guide interne sur les normes de sécurité API pour la conformité aux stores est un complément utile.
La manière mature d'aborder OpenAPI TypeScript est la suivante. Il vous offre un pont strict entre schéma et types, ce qui est excellent, mais il ne valide pas les requêtes, n'impose pas la forme du payload à l'exécution et n'empêche pas un wrapper bâclé de tout compromettre. Le générateur, c'est les 20 % faciles. Le reste, c'est de la conception de pipeline, et c'est là que les équipes gagnent la confiance ou accumulent une fausse assurance.
Générer des types TypeScript à partir d'une spec OpenAPI

Le setup le plus léger et utile est généralement celui qui survit aux changements réels du repo. Gardez la spec OpenAPI dans le même dépôt, générez un fichier de types commité, et rendez les dérives visibles en CI au lieu de compter sur quelqu'un pour se souvenir d'une étape de régénération. Une commande comme npx openapi-typescript ./openapi.yaml -o ./src/types/api.d.ts vous offre un fichier de sortie déterministe que les relecteurs peuvent inspecter comme n'importe quel autre changement de code.
Les flags qui comptent vraiment
Le -o le flag output est important car il rend l'artefact généré explicite. --immutable est utile quand vous voulez que les types générés préservent l'intention readonly dans la sortie, et --alphabetize maintient la stabilité des diffs lorsque l'ordre du schéma change sans impact sémantique. --enum cela compte quand votre équipe préfère les enums dans la surface générée plutôt que les unions.
La documentation du projet est claire sur le périmètre : il s’agit d’un générateur de types, pas d’un runtime client ni d’une couche de requêtes. Cette limite est un atout si vous visez une configuration légère et centrée sur les types. Le dépôt du projet illustre aussi le modèle de maintenance de l’outil, ce qui explique en partie pourquoi les outils open source tiennent le coup en production tant que la documentation et les releases restent actives, comme évoqué dans l’argument en faveur de la maintenance open source. Une lecture concrète de cette posture : le dépôt GitHub et la documentation CLI du projet.
Intégrer la génération dans package.json pour que la commande soit à côté des autres scripts de build, puis l’exécuter à chaque modification de la spec. En CI, régénérer le fichier et échouer si git diff montre un écart. Cela transforme les changements de contrat en travail de revue visible plutôt qu’en risque silencieux au runtime.
Le côté schéma est aussi important que la ligne de commande. Le projet recommande que compilerOptions.noUncheckedIndexedAccess pour que additionalProperties devienne T | undefined, ce qui force un indexage plus sûr aux points d’appel. Il recommande également d’utiliser oneOf seul plutôt que de le mélanger avec d’autres compositions, et de conserver $defs à la racine si l’emplacement est ambigu, car des définitions mal placées peuvent disparaître de la sortie générée. Un dernier détail vous fera gagner du temps plus tard, openapi-typescript ne produira jamais any, de sorte que les détails manquants du schéma sont détectés tôt plutôt que masqués par des types permissifs.
Gardez la spec explicite, sinon le générateur vous renverra fidèlement l’ambiguïté.
Le workflow qui dure est simple. Placez la spec sous contrôle de version, régénérez-l’à chaque build, committez le fichier généré et laissez le type checker signaler les incohérences avant qu’un merge ne passe. Vous obtenez ainsi une frontière de contrat stable pour le reste du pipeline.
Choisir entre types purs, clients complets ou pas de codegen
| Modèle | Temps de build | Fichiers de sortie | Poids du bundle | Le plus adapté |
|---|---|---|---|---|
| Des types purs avec un wrapper léger | Rapide | Peu | Faible | Équipes qui veulent du contrôl’et une surface de runtime réduite |
| Codegen côté client complet | Plus lent | Nombreux | Plus élevé | Équipes qui veulent un handoff rapide et des opérations autogénérées |
| Aucun builder de requêtes généré | Rapide | Aucun ou minimal | Faible | Apps mono-codebase qui privilégient une logique de transport manuelle |
Le choix ne se résume pas à « quel outil gagne ». Il s’agit de savoir quelle forme de pipeline convient à votre repo, à votre équipe et à la quantité de churn que voit API. Dans un benchmark de 2025 portant sur une grande spec OpenAPI d’environ 75 000 lignes, 2 Mo et environ 1 200 opérations, openapi-typescript generated output in about 1,5 seconde en moyenne, contre environ 8,0 secondes pour @hey-api/openapi-ts, 5,5 secondes pour Orval, et 18,1 secondes pour Kubb, tout en produisant un seul fichier de sortie par rapport à 16 pour hey-api, 2,719 pour Orval, et 3,877 pour Kubb (détails du benchmark).
Les types purs privilégient le contrôle
Une approche 100 % types se marie bien avec une couche de requêtes écrite à la main : vous gardez un runtime minimal et une surface API sans surprise. C'est ce qui compte sur les front-ends sensibles au bundler et dans les apps où une seule équipe gère à la fois la spec et le consommateur. Si vous avez besoin d'un rappel que l'expérience développeur n'est pas qu'un simple sucre syntaxique, l' angle de l'expérience développeur est plus facile à évaluer quand votre client code est court, évident et facile à relire.
Les clients complets favorisent la rapidité de passation
openapi-generator, hey-api, Orval, et Kubb essaient tous de faire plus que des types. C'est utile quand vous voulez générer ensemble les méthodes de requête, les modèles et la tuyauterie, surtout lors d'une grosse passation entre les équipes backend et frontend. Le coût est évident dans le benchmark ci-dessus : plus de fichiers générés, une surface de runtime plus large et plus de frictions de build à mesure que la spec grossit.
Aucune codegen ne favorise les refactors locaux
Les builders de requêtes typés et fetch les wrappers fonctionnent bien lorsqu'une seule base de code gère les deux extrémités de la structure et que les modifications de API sont étroitement coordonnées. L'inconvénient est la discipline de maintenance. Plus il y a d'équipes et de dépôts entre le producteur et le consommateur, plus une couche de requêtes manuscrite risque de diverger, à moins d'imposer des tests de contrat de manière agressive.
Le point de décision central n'est pas idéologique. Si votre budget de bundle est serré, les types purs sont attractifs. Si votre équipe souhaite un maximum de scaffolding et peut absorber la sortie, les clients complets réduisent le temps de configuration. Si vous préférez un minimum de pièces mobiles et pouvez garder le contrat proche, les builders de requêtes sans codegen peuvent être le bon compromis interne.
Implémenter un client typé léger autour de Fetch ou Axios

Un wrapper léger est l'endroit où le générateur s'arrête et où commence votre code. Le wrapper doit exposer une fonction par opération, accepter des paramètres et des objets de requête typés, et transmettre l'appel à fetch ou un injecté axios instance injectée sans chercher à faire le malin. Dans la plupart des configurations de production, cette couche fait environ 30 à 60 lignes car les types générés portent déjà l'essentiel de la structure.
Voici le modèle mental qui tient la route :
- Les paramètres de chemin restent typés pour que
/users/{id}ne peut pas être appelé sans unid. - Les objets de requête restent typés so optional filters don’t turn into string soup.
- Le corps des réponses reste typé ainsi, l'analyse de code permet de garantir la structure précise attendue.
Un wrapper de ce type est volontairement basique. Il ne doit pas inventer de retries, de transformations ou de politiques d'auth si celles-ci appartiennent à une autre couche. Son rôl’est de faire passer la requête d'une opération typée vers la couche de transport, puis de renvoyer le résultat typé.
Gardez le wrapper simple et léger en dépendances, sinon chaque modification de codegen impactera l'ensemble de votre app.
L'erreur classique consiste à masquer les incohérences avec as any lorsque les types générés ne correspondent pas à la signature de l'ancien wrapper. Cela donne un build vert, mais une app fragile. Et cela masque précisément la rupture de contrat que le générateur était censé révéler.
Pour les équipes qui préfèrent Axios, le modèl’est identique, seule l'implémentation du transport change. Pour les équipes qui souhaitent un code plus simple côté navigateur, fetch est souvent suffisant. L'essentiel est que la fonction de requête accepte le type de path généré et renvoie une réponse typée, et non un objet au typage flou qu'il faudra retoucher ensuite.
Si vous exploitez bien cette séparation, OpenAPI TypeScript vous offre une répartition claire des tâches. Le schéma vit dans la spec, le transport dans le wrapper, et l’application voit des opérations typées plutôt que des requêtes ad hoc code.
Ajouter une validation au runtime avec zod, ajv ou io-ts
Les types TypeScript disparaissent au runtime, et le réseau n'a rien à faire de la confiance de votre éditeur. C'est pourquoi le pattern sûr n'est pas « générer des types et espérer », mais « générer des types, puis valider à l'edge, là où les données non fiables entrent dans l'app ». Le schéma généré reste la source de vérité, et les bibliothèques de validation comme zod, ajv, et io-ts gérer les vérifications de limites hors de portée des types à la compilation.
Valider dès l'entrée des données
Pour les apps React, l'edge se situe généralement juste après la résolution de la requête et avant que le payload n'entre dans le state. Pour les serveurs, c'est avant que le payload ne soit écrit dans une base de données ou transmis à une règle métier. La règle est simple : gardez la validation proche de la frontière et ne dispersez pas de contrôles manuels dans les fonctionnalités code.
Un zod peut refléter la forme de la réponse générée sans la remplacer :
import { z } from "zod";
const WeatherForecastSchema = z.object({
date: z.string(),
temperatureC: z.number(),
summary: z.string().nullable(),
temperatureF: z.number().optional(),
});
Cet exemple valide les champs que le schéma a marqués comme optionnels, et il garde le contrôl’au runtime aligné sur ce que le générateur a produit. ajv est un excellent choix pour une validation JSON Schema à haut débit sur le serveur, tout en io-ts convient toujours aux équipes qui utilisent déjà le fp-ts style de composition.
La grosse erreur, c’est de valider trop tard. Si le payload entre d’abord dans votre app, le système de types a déjà été contourné et le bug a un endroit où se cacher. Un court guide sur les tests unitaires pour JavaScript s’associe bien à cette approche, car les tests unitaires et la validation aux frontières fonctionnent le mieux lorsqu’ils détectent les mauvaises hypothèses tôt.
Le découpage en couches est prévisible. OpenAPI TypeScript génère le contrat, le validateur vérifie la charge utile au runtime, et votre application code ne voit que les données ayant survécu aux deux étapes. C’est une bien meilleure frontière que de faire confiance à un type statique pour contrôler une réponse non fiable.
Intégrer la génération, la validation et les tests de contrat dans la CI

Un pipeline qui dure transforme le contrat en porte d’entrée, pas en suggestion. Régénérez les types, échouez en cas de dérive, exécutez tsc --noEmit, et testez la forme API contre un mock ou un outil de contrat avant la fusion. Si vous fixez la version du générateur dans package.json, deux ingénieurs ne peuvent pas produire accidentellement des sorties différentes à partir de la même spécification.
Une structure simple pour les GitHub Actions
Voici un workflow pratique :
- Récupérez la spec depuis le repo ou la source générée.
- Régénérez les types.
- Faites échouer le job si
git diffaffiche des changements. - Exécutez
tsc --noEmit. - Exécutez un test de contrat contre un serveur mock tel que Prism ou une vérification via Spectral.
La principale différence entre les tests de contrat et les tests de snapshot tient au périmètre. Les snapshots vous indiquent souvent qu'un fichier a changé. Les tests de contrat vous indiquent si la structure se comporte toujours comme la spec le prévoit.
Un serveur mock est particulièrement utile quand le backend et le frontend sont séparés dans le temps ou par des frontières d'équipe. Il offre au consommateur code une surface API prévisible tout en vérifiant le contrat réel plutôt qu'une fixture codée en dur. Le guide de configuration de l'intégration continue est une référence utile si votre équipe a encore besoin d'une base CI propre et reproductible.
Figer la version du générateur évite l'un des échecs les plus pénibles dans les pipelines de codegen : la dérive invisible des sorties. Si un développeur met à jour le générateur en local et pas un autre, le fichier généré peut devenir une source de bruit aléatoire au lieu d'un signal. La CI doit rendre cela impossible.
Le résultat est un pipeline où les changements de schéma, la génération de types, les vérifications du compilateur et les tests de contrat se renforcent mutuellement. C'est ce qui garantit l'intégrité du workflow.
Pipelines maintenables, performance et checklist finale

Les pipelines qui survivent sont ceux qui ont une gouvernance ennuyeuse. Versionnez la spec, passez en revue les changements de schéma comme code, verrouillez la version du générateur et documentez comment les breaking changes sont approuvés. Si le processus est flou, les équipes le contourneront, et les types générés deviendront de la décoration au lieu d'une contrainte.
Quelques leviers de performance comptent réellement
La génération incrémentale est utile dans les monorepos où la spec change souvent mais où un seul package l'utilise. tsc --incremental peut réduire le travail répétitif du compilateur, et désactiver les flags de sortie dont vous n'avez pas besoin dans les builds de production limite la surface générée. En pratique, le plus gros gain reste social, pas technique, car un pipeline prévisible est exécuté plus souvent qu'un pipeline astucieux.
Voici la checklist à garder sous la main :
- Verrouillage des versions : Verrouillez la
openapi-typescriptversion danspackage.jsonpour éviter que l'output ne diverge d'une machine à l'autre. - Revue du schéma : Treat spec changes as reviewable contract changes, not housekeeping.
- Détection de dérive : Régénérez en CI et échouez en cas de diff.
- Validation edge : Validez les payloads non fiables avant qu’ils n’atteignent l’état de l’app ou le stockage.
- Tests de contrat : Lancez une vérification avec mock qui prouve que le consommateur code correspond toujours au schéma.
- Politique de changements cassants : Notez qui approuve les modifications de structure et comment les clients sont notifiés.
Un pipeline qui intègre ces contrôles ne se contente pas de générer des types, il rend le contrat visible. C’est cette visibilité qui empêche les équipes de faire confiance à un fichier qui ne fait que paraître sûr.
Si vous déployez des applications Capacitor ou Electron et que vous voulez que votre pipeline de mise à jour applique la même rigueur, Capgo vous offre un moyen concret de livrer rapidement des correctifs JavaScript, CSS, de textes, de configuration et d’assets, sans attendre la validation des stores. Visitez Capgo pour voir comment ses bundles signés, sa protection contre les rollbacks et ses contrôles de release s’intègrent à un processus de release qui exige de la rapidité sans perdre le contrôle.