Développement Web

Documenter une API REST avec OpenAPI et Swagger

15 min de lecture

📍 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 components et les références $ref pour 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 sous properties — 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, pas livre).

É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} et POST /livres. Le POST affiche 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 /docs s’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 finalspectral lint ne 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

Pour aller plus loin

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.

Partager