📍 Guide du parcours : Concevoir une API REST — le guide de conception. Cet article approfondit une brique du parcours ; pour la vue d’ensemble, commencez par le guide.
Une documentation qui ne ment jamais
La documentation d’API la plus dangereuse est celle qui est fausse sans qu’on le sache. Un champ renommé, un code de statut changé, un paramètre devenu obligatoire : si la doc vit dans un fichier Word à côté du code, elle dérive en quelques semaines, et les équipes qui consomment l’API perdent des heures à déboguer un comportement que la doc décrit autrement. OpenAPI règle ce problème à la racine : au lieu de décrire l’API en prose, on écrit son contrat dans un format que les machines lisent — pour générer la documentation, valider les requêtes, produire des bouchons et même du code client.
Dans ce tutoriel, vous écrivez le contrat OpenAPI de l’API Catalogue, puis vous le transformez en documentation interactive que n’importe qui peut explorer dans son navigateur. On part de zéro et on construit le contrat avant le serveur — l’approche « design-first », qui aligne tout le monde sur le même contrat dès le premier jour.
🎯 Ce que vous allez apprendre
- Écrire un document OpenAPI 3.2 valide qui décrit des endpoints, des paramètres et des réponses.
- Factoriser vos schémas avec
componentset les références$refpour ne jamais vous répéter. - Déclarer un schéma de sécurité (jeton porteur) réutilisable sur tous les endpoints.
- Servir une documentation interactive Swagger UI depuis un serveur Express.
- Valider votre contrat automatiquement pour qu’il ne dérive jamais.
🛠️ Ce que vous allez construire
Un fichier openapi.yaml qui décrit les endpoints GET /livres, GET /livres/{id} et POST /livres du catalogue, plus un mini-serveur Express qui publie ce contrat sous forme de page Swagger UI cliquable sur http://localhost:3000/docs. À la fin, un collègue peut explorer et tester l’API sans lire une ligne de votre code.
Prérequis
- Node.js 20 ou plus récent (vérifiez avec
node -v) et npm. - Un éditeur de texte. Pour expérimenter sans rien installer, l’éditeur en ligne editor.swagger.io rend l’aperçu en direct.
- Les notions de ressource, verbe HTTP et code de statut — couvertes dans le guide du parcours.
- ⏱️ Temps estimé : environ 45 minutes.
Étape 1 — Comprendre OpenAPI et Swagger, et poser le squelette
Avant de taper, dissipons une confusion fréquente. OpenAPI est la spécification — le format de fichier standard pour décrire une API REST, gouverné par l’OpenAPI Initiative (sous la Linux Foundation). Swagger est une famille d’outils (Swagger UI, Swagger Editor, Swagger Codegen) éditée par SmartBear. Historiquement, le format s’appelait Swagger 2.0 ; il a été donné à l’OpenAPI Initiative fin 2015 et la spécification a été rebaptisée OpenAPI début 2016 — la version 2.0 restant identique à Swagger 2.0, la version 3.0 (2017) en étant la première refonte majeure sous ce nom. Aujourd’hui on écrit « un document OpenAPI » qu’on visualise « avec Swagger UI ».
Un document OpenAPI commence toujours par sa version, un bloc info et la liste des serveurs. Créez un dossier de projet et un fichier openapi.yaml :
openapi: 3.2.0
info:
title: API Catalogue
description: API de gestion du catalogue d'une médiathèque.
version: 1.0.0
servers:
- url: https://catalogue.exemple.org/api
description: Production
- url: http://localhost:3000/api
description: Développement local
paths: {}
Le champ version ici désigne la version de votre API, pas celle d’OpenAPI. La clé paths est vide pour l’instant — on la remplit à l’étape suivante. Au moment d’écrire, la dernière version stable de la spécification est OpenAPI 3.2.0 (sortie en septembre 2025) ; elle est compatible avec la branche 3.1 et s’aligne sur JSON Schema. Si votre outillage n’accepte pas encore 3.2.0, remplacez par 3.1.0 : le contenu de ce tutoriel reste valide.
✅ Point d’étape — Collez ce squelette dans editor.swagger.io. Le volet de droite ne doit afficher aucune erreur rouge (juste un avertissement « no paths defined », normal). Si vous voyez une erreur de version, c’est l’indice que votre éditeur attend
3.1.0.
Étape 2 — Décrire son premier endpoint
Décrivons GET /livres/{id}, qui lit un livre par son identifiant. Un chemin OpenAPI déclare le ou les verbes acceptés, les paramètres, et surtout les réponses possibles avec leur code de statut. C’est cette exhaustivité des réponses qui distingue un bon contrat : on y voit le cas nominal et le cas d’erreur. Remplacez paths: {} par :
paths:
/livres/{id}:
get:
summary: Lire un livre
tags: [Livres]
parameters:
- name: id
in: path
required: true
description: Identifiant du livre
schema:
type: integer
responses:
'200':
description: Le livre demandé
content:
application/json:
schema:
type: object
properties:
id: { type: integer, example: 42 }
titre: { type: string, example: Une si longue lettre }
isbn: { type: string, example: 978-2-7236-1042-1 }
disponible: { type: boolean, example: true }
'404':
description: Aucun livre ne porte cet identifiant
Chaque élément a un rôle : in: path indique que id fait partie de l’URL ; tags regroupe les endpoints par thème dans la documentation ; les clés '200' et '404' (entre guillemets, car YAML les prendrait sinon pour des nombres) énumèrent les réponses. Rechargez l’éditeur : Swagger UI affiche maintenant un endpoint GET /livres/{id} dépliable, avec un bouton « Try it out ».
✅ Point d’étape — Vous devez voir, dans le volet de droite, une section « Livres » contenant l’opération
GET /livres/{id}. En la dépliant, l’exemple de réponse 200 affiche le JSON avec « Une si longue lettre ». Si l’exemple est vide, vérifiez l’indentation sousproperties— c’est l’erreur YAML numéro un.
Étape 3 — Ne jamais se répéter : components et $ref
Le schéma du livre va resservir partout : dans la lecture, dans la liste, dans la création. Le recopier à chaque fois garantit qu’un jour deux copies divergeront. OpenAPI offre une zone components/schemas pour définir un schéma une fois et le référencer ailleurs avec $ref. C’est exactement le principe « Don’t Repeat Yourself » appliqué au contrat.
Ajoutez un bloc components à la racine du fichier (au même niveau que paths) :
components:
schemas:
Livre:
type: object
required: [titre, isbn]
properties:
id: { type: integer, readOnly: true, example: 42 }
titre: { type: string, example: Une si longue lettre }
isbn: { type: string, pattern: '^97[89]-', example: 978-2-7236-1042-1 }
annee: { type: integer, example: 1979 }
disponible: { type: boolean, default: true }
Deux détails qui paient : readOnly: true sur id dit que le client ne fournit jamais l’identifiant (c’est le serveur qui l’attribue), et required liste les champs obligatoires à la création. Remplacez maintenant le gros schéma en ligne de l’étape 2 par une simple référence :
'200':
description: Le livre demandé
content:
application/json:
schema:
$ref: '#/components/schemas/Livre'
Le #/components/schemas/Livre est un pointeur interne au document. Tout outil qui lit le contrat le résout automatiquement. À partir d’ici, le schéma du livre a une seule source de vérité.
✅ Point d’étape — Le rendu Swagger UI doit être identique à l’étape 2 : la référence n’a rien changé pour le lecteur, seulement pour vous. Si Swagger UI affiche « Could not resolve reference », c’est presque toujours une faute de frappe dans le chemin du
$ref(sensible à la casse :Livre, paslivre).
Étape 4 — Décrire une écriture : POST /livres
La lecture, c’est la moitié facile. Une création introduit un corps de requête (requestBody) et de nouveaux codes de statut : 201 en cas de succès, 422 si les données sont invalides. Ajoutez sous /livres/{id} un nouveau chemin /livres :
/livres:
post:
summary: Ajouter un livre au catalogue
tags: [Livres]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Livre'
responses:
'201':
description: Livre créé
headers:
Location:
description: URL du livre créé
schema: { type: string }
content:
application/json:
schema:
$ref: '#/components/schemas/Livre'
'422':
description: Données invalides (ISBN malformé, titre manquant…)
Remarquez qu’on réutilise le même schéma Livre en entrée et en sortie : grâce à readOnly, les outils savent que id n’est pas attendu dans le corps du POST mais figure dans la réponse. On déclare aussi l’en-tête Location de la réponse 201, conformément à la bonne pratique vue dans le guide : après une création, on indique où trouver la ressource.
✅ Point d’étape — La section « Livres » contient désormais deux opérations :
GET /livres/{id}etPOST /livres. LePOSTaffiche un corps de requête éditable et deux réponses (201, 422).
Étape 5 — Déclarer la sécurité une fois pour toutes
La lecture du catalogue est publique, mais ajouter un livre exige un jeton. OpenAPI permet de définir des securitySchemes dans components, puis de les appliquer globalement ou endpoint par endpoint. Déclarez un schéma de type « jeton porteur » :
components:
securitySchemes:
bearerJWT:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
# … le schéma Livre déjà défini …
Puis exigez ce jeton sur la création seule, en ajoutant une clé security à l’opération post :
post:
summary: Ajouter un livre au catalogue
security:
- bearerJWT: []
tags: [Livres]
# … le reste de l'opération …
Swagger UI affiche maintenant un cadenas sur le POST et un bouton « Authorize » global : l’explorateur peut coller un jeton et tester l’endpoint protégé. Le détail du fonctionnement de ces jetons est l’objet du tutoriel Authentifier une API REST ; ici, l’important est que le contrat déclare la sécurité, ce qui la rend visible et testable.
Étape 6 — Servir Swagger UI depuis Express
L’éditeur en ligne, c’est pour expérimenter. En vrai, on héberge la documentation à côté de l’API, pour qu’elle soit toujours synchrone avec le serveur déployé. On utilise swagger-ui-express, qui transforme un document OpenAPI en page interactive, et js-yaml pour charger le fichier. Initialisez le projet et installez les dépendances :
npm init -y
npm install express swagger-ui-express js-yaml
Au moment d’écrire, swagger-ui-express est en version 5.0.1 et fonctionne avec Express 5. Créez ensuite server.js :
const express = require('express');
const swaggerUi = require('swagger-ui-express');
const yaml = require('js-yaml');
const fs = require('fs');
const app = express();
// Charge et parse le contrat OpenAPI une fois au démarrage
const contrat = yaml.load(fs.readFileSync('./openapi.yaml', 'utf8'));
// Publie la doc interactive sur /docs
app.use('/docs', swaggerUi.serve, swaggerUi.setup(contrat));
app.listen(3000, () => {
console.log('Documentation : http://localhost:3000/docs');
});
Lancez avec node server.js, puis ouvrez http://localhost:3000/docs. Vous devriez voir exactement la même interface que dans l’éditeur en ligne, mais servie par votre serveur. Le contrat est lu au démarrage ; à chaque modification de openapi.yaml, il suffit de redémarrer pour que la doc reflète le changement.
✅ Point d’étape — La page
/docss’affiche avec les opérations Livres et le bouton « Authorize ». Si vous obtenez « Cannot GET /docs », vérifiez que le serveur tourne bien et qu’aucune autre application n’occupe le port 3000.
Étape 7 — Valider le contrat pour qu’il ne dérive jamais
Un contrat n’a de valeur que s’il reste valide. On automatise sa vérification avec un « linter » comme Spectral (édité par Stoplight), qui détecte les erreurs de structure et les entorses aux bonnes pratiques. On peut le lancer sans installation permanente via npx :
npx @stoplight/spectral-cli lint openapi.yaml
Sur un contrat propre, la sortie se termine par « No results with a severity of ‘error’ found! ». Spectral signale aussi les manques de qualité (un endpoint sans description, une réponse d’erreur absente), ce qui pousse à documenter complètement. Brancher cette commande dans votre intégration continue garantit qu’aucune modification ne casse le contrat sans qu’on le sache.
✅ Point d’étape final —
spectral lintne renvoie aucune erreur de sévérité « error ». Votre contrat décrit trois opérations, un schéma factorisé et un schéma de sécurité, et il se valide tout seul. La brique « documentation » de l’API est en place.
🐞 Pièges fréquents
| Symptôme | Cause probable | Correctif |
|---|---|---|
| « bad indentation of a mapping entry » | Mélange d’espaces et de tabulations en YAML | Indenter uniquement avec des espaces (2 par niveau) |
| « Could not resolve reference » | Chemin de $ref erroné ou casse incorrecte |
Vérifier #/components/schemas/Livre au caractère près |
Le statut 200 devient le nombre 200 et tout casse |
Clés de statut non mises entre guillemets | Toujours écrire '200', '404' avec des guillemets |
nullable: true rejeté par le validateur |
OpenAPI 3.1+ a retiré nullable |
Écrire type: [string, 'null'] à la place |
| Swagger UI affiche une page blanche | Contrat non parsé (YAML invalide) avant le setup |
Valider d’abord avec Spectral ou l’éditeur en ligne |
🌍 Réalités du terrain
Le contrat OpenAPI rend un service précieux quand les équipes travaillent en décalé ou avec une connexion intermittente : il sert de point de synchronisation indépendant du serveur. À partir du seul openapi.yaml, on peut générer un bouchon (mock) qui répond avec les exemples du contrat — l’équipe front développe alors sans attendre que le back soit prêt. Des outils comme Prism (Stoplight) démarrent un serveur de mock en une commande à partir du fichier.
Pour la documentation publique, gardez aussi en tête le poids de la page. Swagger UI embarque un volume de JavaScript non négligeable ; quand la documentation cible des lecteurs sur réseau mobile, une alternative comme Redoc produit une page plus légère et lisible sur petit écran, à partir du même contrat. Le contrat, lui, ne change pas : c’est tout l’intérêt d’avoir séparé le fond (OpenAPI) de la forme (l’outil de rendu).
✅ Récapitulatif
Vous êtes parti d’un fichier vide et vous repartez avec un contrat OpenAPI 3.2 qui décrit la lecture et la création de livres, factorise son schéma avec $ref, déclare un schéma de sécurité, se publie en documentation interactive via Express et se valide automatiquement. Surtout, vous avez adopté le réflexe « design-first » : le contrat existe avant le code, et il devient la référence unique pour toutes les équipes. La documentation ne peut plus mentir, parce qu’elle est la définition de l’API.
🧾 Aide-mémoire
| Élément | Rôle |
|---|---|
openapi: 3.2.0 |
Version de la spécification utilisée |
paths |
Les endpoints et leurs opérations |
components/schemas |
Schémas réutilisables référencés par $ref |
components/securitySchemes |
Modes d’authentification déclarés |
$ref: '#/components/schemas/X' |
Pointeur interne vers un schéma |
readOnly: true |
Champ renvoyé par le serveur, jamais fourni par le client |
npx @stoplight/spectral-cli lint |
Valider le contrat |
💪 À vous de jouer
Ajoutez au contrat l’endpoint GET /livres (la liste de tous les livres), avec une réponse 200 dont le schéma est un tableau de Livre. Pensez à le placer sous le chemin /livres, à côté du post existant.
Voir une solution
/livres:
get:
summary: Lister les livres
tags: [Livres]
responses:
'200':
description: La liste des livres
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Livre'
post:
# … l'opération post déjà écrite …
Le tableau réutilise le schéma Livre via items : encore une fois, aucune duplication. La pagination de cette liste fait l’objet d’un tutoriel dédié.
Tutoriels frères
- Authentifier une API REST : JWT, OAuth 2 et clés — donner vie au schéma de sécurité déclaré ici.
- Pagination, filtres et tri — documenter et implémenter une liste de livres qui passe à l’échelle.
Pour aller plus loin
- 🔝 Retour au guide : Concevoir une API REST.
- Spécification OpenAPI 3.2.0 : spec.openapis.org/oas/v3.2.0.html.
- Éditeur Swagger en ligne : editor.swagger.io ; documentation de
swagger-ui-express: npmjs.com/package/swagger-ui-express.
FAQ
Design-first ou code-first ? En « code-first », on génère le contrat à partir d’annotations dans le code (des frameworks comme FastAPI le produisent automatiquement). En « design-first », on écrit le contrat d’abord. Le design-first aligne les équipes plus tôt et évite que le contrat ne soit qu’un reflet a posteriori du code ; c’est l’approche recommandée pour une API conçue à plusieurs.
YAML ou JSON pour le document OpenAPI ? Les deux sont équivalents ; tout outil lit l’un et l’autre. YAML est plus agréable à écrire et à relire à la main (moins de ponctuation), JSON est parfois imposé par une chaîne d’outils. Le contenu est identique.
Faut-il versionner le fichier openapi.yaml dans Git ? Absolument. Le contrat est du code : il vit dans le dépôt, passe en revue de code, et son historique raconte l’évolution de l’API. C’est aussi ce qui permet de détecter automatiquement un changement cassant entre deux versions.
Swagger UI expose-t-il un risque en production ? La page elle-même est inoffensive, mais elle révèle toute la surface de l’API. Sur une API interne, on protège souvent /docs derrière une authentification, ou on ne publie que la documentation des endpoints réellement publics.
Mots-clés : OpenAPI 3.2, Swagger UI, documentation API, design-first, contrat d’API, swagger-ui-express, Spectral, components $ref.