Développement Web

Concevoir une API REST : le guide de conception

19 دقائق للقراءة

Pourquoi la conception d’une API mérite qu’on s’y arrête

Une API REST mal conçue ne tombe pas en panne le jour de la mise en production. Elle se venge plus tard : six mois après, l’équipe mobile attend un champ qui n’existe pas, un partenaire scrape la moitié du catalogue parce que la pagination renvoie tout d’un coup, et personne n’ose renommer un endpoint de peur de casser trois applications clientes dont on ignore l’existence. Une bonne conception, à l’inverse, se remarque à ce qu’on ne la remarque pas : les développeurs qui consomment l’API devinent l’URL du prochain endpoint sans lire la documentation, les erreurs sont compréhensibles, et faire évoluer le service ne demande pas de tout réécrire.

Ce guide est la carte d’ensemble d’un parcours complet de conception d’API REST. Il pose le vocabulaire et les principes : ce qu’est vraiment REST, comment modéliser des ressources, choisir les verbes HTTP, renvoyer les bons codes de statut, comprendre l’idempotence et la négociation de contenu. Les tutoriels rattachés creusent ensuite chaque brique pas à pas : documenter avec OpenAPI, authentifier, paginer, versionner, sécuriser. À la fin, vous saurez concevoir une API qu’on a envie d’utiliser — pas seulement une qui fonctionne.

🎯 Ce que ce parcours vous permettra de faire

  • Modéliser un domaine métier en ressources et en URI cohérentes, lisibles et prévisibles.
  • Choisir le bon verbe HTTP et le bon code de statut pour chaque opération, en vous appuyant sur la sémantique HTTP normalisée (RFC 9110).
  • Distinguer les méthodes sûres des méthodes idempotentes, et concevoir des endpoints qu’on peut rejouer sans dégât.
  • Rédiger un contrat d’API exécutable avec OpenAPI et le publier en documentation interactive.
  • Protéger l’accès avec des jetons (JWT), un flux OAuth 2 ou des clés d’API, selon le type de client.
  • Paginer, filtrer et trier de gros volumes sans écrouler la base, versionner l’API et déprécier proprement, puis durcir le service face aux abus.

🛠️ Le projet fil rouge : l’API du catalogue d’une médiathèque

Pour que rien ne reste abstrait, tout le parcours construit la même API : le catalogue d’une médiathèque. On y gère des livres, leurs auteurs, les adhérents et les emprunts. C’est un domaine assez riche pour rencontrer tous les cas intéressants — collections, sous-ressources, relations, états qui changent — et assez familier pour qu’on n’ait jamais à expliquer le métier.

Voici l’allure d’une réponse que notre API renverra, pour fixer les idées dès maintenant :

GET /api/livres/42 HTTP/1.1
Host: catalogue.exemple.org
Accept: application/json

HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 42,
  "titre": "Une si longue lettre",
  "isbn": "978-2-7236-1042-1",
  "annee": 1979,
  "auteur": { "id": 7, "nom": "Mariama Bâ" },
  "disponible": true
}

Chaque tutoriel du parcours ajoute une brique à cette API : la documentation OpenAPI décrit ce GET /api/livres/42, l’authentification protège la création d’un emprunt, la pagination s’applique à GET /api/livres, le versionnage gère le passage de auteur à une liste d’auteurs, et la couche sécurité encadre les écritures.

🗺️ Le parcours d’apprentissage, dans l’ordre

Vous pouvez lire ce guide seul pour la vue d’ensemble, puis suivre les tutoriels dans l’ordre ci-dessous. Chacun est autonome mais ils se complètent :

  1. Documenter une API REST avec OpenAPI et Swagger — écrire le contrat de l’API avant le code, et le transformer en documentation interactive.
  2. Authentifier une API REST : JWT, OAuth 2 et clés d’API — savoir qui appelle, et lui donner exactement les droits qu’il faut.
  3. Pagination, filtres et tri dans une API REST — renvoyer de gros volumes sans tout charger, et laisser le client cibler ce qu’il veut.
  4. Versionner et déprécier une API REST proprement — faire évoluer le contrat sans casser les clients existants.
  5. Sécuriser une API REST : rate limiting, CORS et validation — protéger le service des abus, des navigateurs et des données malformées.

REST, au-delà du mot à la mode

REST n’est pas une technologie ni une bibliothèque : c’est un style d’architecture décrit en 2000 par Roy Fielding dans sa thèse de doctorat, au moment où il co-rédigeait les spécifications de HTTP. L’idée centrale est simple : le Web fonctionne déjà très bien à l’échelle de la planète, alors construisons nos API sur les mêmes principes que le Web — des ressources identifiées par des URI, manipulées via un petit ensemble de verbes standard, avec des représentations qu’on peut mettre en cache.

Fielding énonce un jeu de contraintes. Une API « RESTful » les respecte ; beaucoup d’API se disent REST sans toutes les suivre, et ce n’est pas grave tant qu’on sait ce qu’on abandonne. Les contraintes principales :

  • Client-serveur : l’interface sépare nettement celui qui stocke les données de celui qui les affiche. Les deux évoluent indépendamment.
  • Sans état (stateless) : chaque requête contient tout ce qu’il faut pour être traitée. Le serveur ne garde pas de « session » en mémoire entre deux appels. C’est ce qui permet de mettre dix serveurs derrière un répartiteur de charge sans se soucier de savoir lequel a vu la requête précédente.
  • Cache : une réponse indique si elle peut être mise en cache et pour combien de temps. Bien exploité, le cache élimine une part énorme du trafic.
  • Interface uniforme : c’est le cœur de REST. Les mêmes conventions (URI, verbes, codes) s’appliquent partout, si bien qu’un développeur qui connaît une API REST en comprend une autre à moitié.
  • Système en couches : entre le client et le serveur peuvent s’intercaler des proxys, des caches, des passerelles, sans que le client ait à le savoir.

Retenez surtout l’interface uniforme et le « sans état ». Ce sont les deux contraintes qui changent concrètement votre conception, et celles que les tutoriels du parcours déclinent en pratique.

Modéliser des ressources : la première décision

Une ressource est « une chose » que l’API expose : un livre, un auteur, un emprunt. La règle de base du nommage tient en une phrase : des noms au pluriel pour les collections, pas des verbes. On écrit /livres, jamais /getLivres ni /creerLivre — le verbe, c’est la méthode HTTP qui le porte, pas l’URL.

À partir de là, la structure se déduit :

/livres                 → la collection de tous les livres
/livres/42              → le livre dont l'identifiant est 42
/auteurs/7              → l'auteur 7
/auteurs/7/livres       → les livres de l'auteur 7 (sous-ressource)
/adherents/15/emprunts  → les emprunts en cours de l'adhérent 15

Quelques principes qui évitent les regrets :

  • Hiérarchie peu profonde. Au-delà de /collection/id/sous-collection/id, l’URL devient illisible. Pour aller plus loin dans les relations, on filtre par paramètre de requête plutôt que d’empiler les segments.
  • Identifiants stables. L’id d’une ressource ne doit pas être un détail interne fragile. Beaucoup d’API exposent un identifiant opaque (UUID) plutôt que la clé primaire auto-incrémentée, pour ne pas révéler le volume de données ni faciliter l’énumération.
  • Cohérence absolue. Si c’est /livres au pluriel, alors c’est /auteurs et /emprunts, jamais /auteur au singulier ailleurs. L’incohérence force le client à consulter la doc pour chaque endpoint, ce qui ruine l’intérêt d’une interface uniforme.

Du métier aux endpoints : une modélisation pas à pas

Voyons comment passer d’un besoin métier à un jeu d’endpoints, sur le cas de l’emprunt. La tentation du débutant est d’écrire un endpoint « action » : POST /emprunterLivre. Cela fonctionne, mais on retombe dans le RPC : chaque nouvelle action ajoute un verbe inventé, et l’API perd sa régularité.

L’approche REST demande : « quelle chose apparaît quand on emprunte un livre ? » Réponse : un emprunt. L’emprunt est une ressource à part entière — il a une date, un adhérent, une date de retour prévue, un état. On le modélise donc comme une collection :

POST /api/livres/42/emprunts     → créer un emprunt sur le livre 42 (le sort)
GET  /api/adherents/15/emprunts  → lister les emprunts de l'adhérent 15
PATCH /api/emprunts/108          → marquer l'emprunt 108 comme retourné
GET  /api/emprunts?statut=en_retard → tous les emprunts en retard

Le bénéfice est immédiat : l’historique des emprunts existe gratuitement (c’est la collection), l’état du livre se déduit de ses emprunts, et « rendre un livre » n’est pas une action magique mais une simple transition d’état sur la ressource emprunt. Cette gymnastique — transformer un verbe métier en ressource — est le réflexe central de la conception REST. Quand vous butez sur « comment exposer telle action ? », cherchez le nom qui se cache derrière le verbe.

Les verbes HTTP : cinq mots pour tout dire

La même URL change de sens selon le verbe. /livres/42 avec GET lit le livre ; avec DELETE, le supprime. C’est l’application directe de l’interface uniforme : au lieu d’inventer une fonction par opération, on combine un petit jeu de verbes avec les URI.

Verbe Rôle Exemple sur le catalogue
GET Lire une ressource ou une collection GET /livres/42
POST Créer une ressource dans une collection POST /livres
PUT Remplacer entièrement une ressource PUT /livres/42
PATCH Modifier partiellement une ressource PATCH /livres/42
DELETE Supprimer une ressource DELETE /livres/42

La distinction PUT contre PATCH piège souvent les débutants. PUT envoie la représentation complète : tout champ absent du corps est considéré comme effacé. PATCH (normalisé par la RFC 5789) envoie seulement les champs à changer. Pour passer un livre de « emprunté » à « disponible », un PATCH /livres/42 avec {"disponible": true} est le bon outil ; un PUT obligerait à renvoyer le titre, l’ISBN, l’année et le reste, sous peine de les perdre.

Sûreté et idempotence : la notion la plus rentable à comprendre

Deux propriétés des verbes décident de la robustesse de votre API. Une méthode est sûre si elle ne modifie rien côté serveur — c’est le cas de GET et HEAD. Une méthode est idempotente si l’exécuter une fois ou cinq fois de suite produit le même état final.

Verbe Sûr ? Idempotent ?
GET, HEAD Oui Oui
PUT Non Oui
DELETE Non Oui
POST Non Non
PATCH Non Non (pas garanti)

Pourquoi est-ce si rentable ? Parce que les réseaux mentent. Un client envoie POST /emprunts, la connexion se coupe juste avant que la réponse n’arrive, le client réessaie : vous venez peut-être de créer deux emprunts pour le même livre. Comme POST n’est pas idempotent, vous devez gérer ce cas — typiquement avec une clé d’idempotence (un en-tête Idempotency-Key fourni par le client, que le serveur mémorise pour détecter le rejeu). À l’inverse, PUT /livres/42 peut être renvoyé sans crainte : le résultat est le même. Concevoir en pensant idempotence, c’est concevoir pour un monde où les requêtes se perdent — c’est-à-dire le monde réel.

Les codes de statut : parler le langage de HTTP

Le code de statut est la première information que lit un client : il dit si ça a marché, et sinon, à qui la faute. La sémantique est normalisée par la RFC 9110 (HTTP Semantics, 2022, qui a le statut de norme Internet). Renvoyer 200 sur une erreur avec un message « error » dans le corps est l’anti-patron classique : les caches, les proxys et les bibliothèques clientes se fient au code, pas à votre prose.

Code Sens Quand l’utiliser
200 OK Succès Lecture ou mise à jour réussie
201 Created Ressource créée Après un POST qui crée ; renvoyer l’en-tête Location
204 No Content Succès sans corps Après un DELETE ou un PUT sans retour utile
400 Bad Request Requête malformée JSON invalide, syntaxe incorrecte
401 Unauthorized Non authentifié Jeton absent ou invalide
403 Forbidden Authentifié mais pas autorisé L’adhérent veut supprimer un livre
404 Not Found Ressource introuvable /livres/99999 n’existe pas
409 Conflict Conflit d’état Emprunter un livre déjà emprunté
422 Unprocessable Content Données invalides JSON correct mais ISBN non conforme (RFC 9110)
429 Too Many Requests Trop de requêtes Rate limiting dépassé (RFC 6585)
500 Internal Server Error Erreur serveur Bogue non géré — jamais à cause du client

La nuance 400 contre 422 revient souvent. 400 signifie « je n’ai même pas compris ta requête » (le JSON est cassé). 422, défini désormais dans la RFC 9110 sous le nom « Unprocessable Content », signifie « j’ai compris le JSON, mais son contenu viole une règle métier » (l’ISBN a la bonne forme mais ne respecte pas la clé de contrôle). Et la différence 401 contre 403 mérite d’être tatouée : 401 = « je ne sais pas qui tu es », 403 = « je sais qui tu es, et tu n’as pas le droit ».

Représentations et négociation de contenu

Une ressource n’est pas ses octets : c’est un concept. Le livre 42 peut être représenté en JSON, en XML, demain en un autre format. Le client annonce ce qu’il sait lire avec l’en-tête Accept, le serveur répond avec Content-Type. C’est la négociation de contenu.

GET /api/livres/42 HTTP/1.1
Accept: application/json

HTTP/1.1 200 OK
Content-Type: application/json

En pratique, la quasi-totalité des API modernes servent du JSON et s’en tiennent là — c’est un choix raisonnable. Mais comprendre la mécanique de la négociation est ce qui rend possible, plus tard, le versionnage par type de média (Accept: application/vnd.catalogue.v2+json), une technique que le tutoriel sur le versionnage détaille.

Le modèle de maturité de Richardson

Leonard Richardson a proposé une échelle, popularisée par Martin Fowler, pour situer le degré de « RESTfulness » d’une API. Elle aide à comprendre ce qu’on gagne en montant les marches :

  • Niveau 0 : une seule URL, tout passe en POST avec un corps qui décrit l’action. C’est du RPC déguisé en HTTP.
  • Niveau 1 : on introduit des ressources (/livres, /auteurs) mais toujours un seul verbe.
  • Niveau 2 : on utilise les verbes HTTP et les codes de statut pour ce qu’ils valent. C’est ici que vivent la plupart des API qu’on appelle « REST », et c’est un objectif parfaitement respectable.
  • Niveau 3 : les réponses contiennent des liens hypermédia qui indiquent au client les actions possibles ensuite — c’est HATEOAS (Hypermedia As The Engine Of Application State).

Concrètement, le niveau 3 ajoute à la réponse du livre des liens vers les actions disponibles :

{
  "id": 42,
  "titre": "Une si longue lettre",
  "disponible": true,
  "_links": {
    "self":     { "href": "/api/livres/42" },
    "emprunter": { "href": "/api/livres/42/emprunts", "method": "POST" }
  }
}

HATEOAS est élégant mais exigeant : peu d’API publiques l’implémentent intégralement, car peu de clients l’exploitent. Visez d’abord un niveau 2 propre et complet ; le niveau 3 est un raffinement, pas un prérequis.

Gérer les erreurs de façon exploitable

Une erreur n’est utile que si le client peut l’analyser par programme. Renvoyer {"error": "quelque chose s'est mal passé"} oblige chaque client à parser du texte libre. La RFC 9457 (« Problem Details for HTTP APIs », 2023, qui remplace la RFC 7807) propose un format standard, servi avec le type de média application/problem+json :

HTTP/1.1 409 Conflict
Content-Type: application/problem+json

{
  "type": "https://catalogue.exemple.org/erreurs/livre-deja-emprunte",
  "title": "Le livre est déjà emprunté",
  "status": 409,
  "detail": "Le livre 42 a été emprunté par l'adhérent 15 le 12 mai.",
  "instance": "/api/livres/42/emprunts"
}

Le champ type est une URI stable qui identifie la catégorie d’erreur — le client peut s’y brancher sans lire le detail, qui, lui, est destiné aux humains. Adopter ce format dès le départ uniformise toute la gestion d’erreurs de l’API.

Tour d’horizon pratique : les briques du parcours

Avec ces fondations, chaque tutoriel devient une étape de construction de l’API Catalogue :

  • Le contrat d’abord. Avant d’écrire une ligne de serveur, on décrit l’API en OpenAPI. Le contrat sert de référence partagée entre le back, le front et les partenaires, et génère une documentation interactive. → Documenter une API REST avec OpenAPI et Swagger.
  • Savoir qui appelle. Lire le catalogue est public ; créer un emprunt ne l’est pas. On distingue authentification (qui es-tu ?) et autorisation (as-tu le droit ?), avec JWT, OAuth 2 ou clés d’API. → Authentifier une API REST.
  • Ne pas tout renvoyer. GET /livres sur dix mille titres ne doit pas tout charger. Pagination, filtres et tri laissent le client cibler exactement ce qu’il veut. → Pagination, filtres et tri.
  • Évoluer sans casser. Le jour où un livre a plusieurs auteurs, il faut changer le contrat sans briser les applications déjà déployées. C’est tout l’art du versionnage et de la dépréciation. → Versionner et déprécier.
  • Tenir le choc. Une API ouverte attire les abus, les navigateurs imposent CORS, et toute donnée entrante est suspecte jusqu’à preuve du contraire. → Sécuriser une API REST.

Réalités du terrain

Les principes ci-dessus ne vivent pas dans le vide : ils rencontrent des contraintes très concrètes qui orientent les choix de conception.

La latence et la bande passante comptent. Quand une part importante des clients consomme l’API depuis un téléphone sur un réseau mobile irrégulier, chaque kilo-octet et chaque aller-retour se paient. Cela pousse à concevoir des réponses compactes, à permettre au client de ne demander que les champs utiles (sparse fieldsets), à activer la compression gzip et à exploiter le cache HTTP (ETag, Cache-Control) pour éviter de retransmettre ce qui n’a pas changé. Une API bavarde qui renvoie trois cents champs là où le client en lit cinq est un coût permanent imposé à tous ses utilisateurs.

Le coût d’hébergement est réel. Une pagination absente ou une absence de rate limiting se traduit directement en facture : requêtes lourdes, base sollicitée inutilement, bande passante sortante. Concevoir économe — pagination par défaut, limites raisonnables, cache — n’est pas une optimisation prématurée, c’est une question de soutenabilité quand le budget serveur est compté.

Le développement n’est pas toujours connecté. Un contrat OpenAPI permet de générer des bouchons (mocks) et de travailler le front sans back disponible, ce qui est précieux quand la connexion est intermittente ou que les équipes ne sont pas au même endroit. Le contrat devient le point de synchronisation, indépendant de la disponibilité réseau.

Erreurs fréquentes à éviter

Erreur Pourquoi c’est un problème La bonne pratique
Des verbes dans l’URL (/getLivre, /deleteLivre) Casse l’interface uniforme ; chaque endpoint devient à apprendre Noms de ressources + verbes HTTP
Tout renvoyer en 200, même les erreurs Caches et clients se fient au code, pas au corps Le code de statut reflète le résultat
Confondre 401 et 403 Le client ne sait pas s’il doit se reconnecter ou abandonner 401 = non identifié, 403 = interdit
Exposer la clé primaire auto-incrémentée Révèle le volume, facilite l’énumération Identifiant opaque (UUID) si la donnée est sensible
GET qui modifie des données GET doit être sûr ; les caches et préchargeurs le rejouent Toute écriture passe par POST/PUT/PATCH/DELETE
Pas de pagination sur les collections Une seule requête peut écrouler la base Pagination par défaut, taille de page plafonnée

FAQ

REST ou GraphQL ? Les deux résolvent des problèmes différents. REST s’appuie sur HTTP (cache, codes, verbes) et brille pour des ressources bien identifiées et un cache efficace. GraphQL excelle quand les clients ont des besoins de données très variés et veulent éviter le sur- ou sous-chargement. Beaucoup d’organisations utilisent les deux. Maîtriser REST reste la base : GraphQL lui-même se sert souvent au-dessus de HTTP.

Faut-il absolument faire du HATEOAS pour « être REST » ? Selon la définition stricte de Fielding, oui. En pratique, l’immense majorité des API utiles s’arrêtent au niveau 2 du modèle de Richardson et personne ne s’en plaint. Visez la cohérence et la justesse des verbes et des codes avant l’hypermédia.

Pluriel ou singulier dans les URI ? Le pluriel (/livres, /livres/42) est la convention la plus répandue et la plus lisible. L’essentiel n’est pas le choix lui-même mais sa constance : une seule règle, partout.

Où mettre la version de l’API ? Les deux approches courantes sont le préfixe d’URI (/v1/livres) et le type de média (Accept: application/vnd.catalogue.v2+json). Le préfixe est plus simple et plus visible ; le type de média est plus « pur » mais moins lisible. Le tutoriel dédié pèse les deux.

Quel code renvoyer après une création ? 201 Created, accompagné d’un en-tête Location pointant vers la ressource créée, et idéalement de sa représentation dans le corps. C’est plus précis qu’un simple 200.

Comment gérer les actions qui ne sont pas un simple CRUD ? Pour « emprunter un livre », deux écoles : modéliser une sous-ressource (POST /livres/42/emprunts, l’approche la plus RESTful) ou, à défaut, une « action » assumée. Préférez toujours modéliser l’état comme une ressource quand c’est possible — ici, l’emprunt est une ressource.

Ressources et références

Mots-clés : conception API REST, ressources HTTP, verbes HTTP, codes de statut, idempotence, modèle de maturité de Richardson, HATEOAS, négociation de contenu.

مشاركة