Passer au contenu principal
RM
Retour au blog

Contraintes REST, HATEOAS, idempotence — pourquoi tout le monde utilise le mot REST mais presque personne ne le fait correctement.

Radnoumane Mossabely9 min read
API REST expliqué
REST
API
HTTP
Architecture
Backend
0 vues

TL;DR

  • REST n'est pas "du JSON sur HTTP." C'est un style architectural avec 6 contraintes definies par Roy Fielding en 2000.
  • HATEOAS (l'hypermedia) est la contrainte la plus ignoree. Quasiment personne ne l'implemente, et c'est souvent ok.
  • L'idempotence est le concept le plus utile au quotidien : GET, PUT, DELETE sont idempotents, POST ne l'est pas.
  • Les status codes HTTP ont un sens precis. Les utiliser correctement evite 80% des bugs d'integration.
  • REST vs GraphQL vs gRPC : chacun a son domaine. REST reste le defaut raisonnable pour la majorite des cas.
  • La verite : 90% des "API REST" sont du JSON sur HTTP, et c'est parfaitement acceptable.

Le malentendu REST

Si tu demandes a un dev "c'est quoi une API REST", tu vas entendre : "c'est une API qui utilise des verbes HTTP pour manipuler des ressources en JSON." C'est pas faux. Mais c'est comme dire qu'une voiture "c'est un truc avec 4 roues qui avance." Techniquement correct, fondamentalement incomplet.

REST, c'est un style architectural defini par Roy Fielding dans sa these de doctorat en 2000. Oui, une these de doctorat. Et le mec ne parlait pas de JSON (qui n'existait pas encore) ni meme d'API au sens ou on l'entend aujourd'hui. Il decrivait l'architecture du web lui-meme.

Comprendre REST en profondeur, ca ne sert pas a impressionner en entretien (enfin, un peu). Ca sert a prendre de meilleures decisions quand tu concois une API.

Les 6 contraintes de Fielding

REST definit 6 contraintes architecturales. Si ton API respecte les 6, elle est RESTful. Si elle en respecte 4 sur 6, elle est "RESTish." Si elle en respecte 2, c'est du JSON sur HTTP. Et c'est souvent tres bien.

1. Client-Server. Le client et le serveur sont separes. Le client ne sait rien du stockage des donnees. Le serveur ne sait rien de l'interface utilisateur. Ca parait evident aujourd'hui, mais en 2000 c'etait une prise de position.

2. Stateless. Chaque requete du client contient TOUTE l'information necessaire au serveur pour la traiter. Le serveur ne stocke aucun etat de session entre les requetes. C'est pour ca que tu envoies un token JWT a chaque requete au lieu de dependre d'une session cote serveur.

La contrainte stateless est la plus violee en pratique. Les cookies de session, les tokens CSRF, les sticky sessions -- tout ca est du state cote serveur. Est-ce grave ? Non, tant que tu comprends le trade-off (scalabilite contre simplicite).

3. Cacheable. Les reponses doivent indiquer explicitement si elles sont cacheables ou non. C'est a ca que servent les headers Cache-Control, ETag, et Last-Modified. Un client qui cache correctement peut eviter 50-80% des requetes au serveur.

4. Uniform Interface. C'est la contrainte la plus importante et la plus mal comprise. Elle se decompose en 4 sous-contraintes :

  • Identification des ressources (via URI)
  • Manipulation via representations (JSON, XML, etc.)
  • Messages auto-descriptifs (le content-type dit ce que c'est)
  • HATEOAS (on y revient)

5. Layered System. Le client ne sait pas s'il parle au serveur final ou a un intermediaire (proxy, load balancer, CDN). Ca permet d'ajouter des couches (cache, securite, monitoring) sans modifier le client.

6. Code on Demand (optionnel). Le serveur peut envoyer du code executable au client (JavaScript, applets). C'est la seule contrainte optionnelle, et c'est ironiquement la base du web moderne.

HATEOAS : le fantome de REST

HATEOAS. Hypermedia As The Engine Of Application State. C'est le nom le plus intimidant de l'informatique, et c'est la contrainte que personne n'implemente.

Le principe : les reponses de l'API doivent contenir des liens vers les actions possibles. Le client ne code pas les URL en dur -- il les decouvre dynamiquement.

hljs json
// Sans HATEOAS (ce que tout le monde fait)
{
  "id": 42,
  "name": "John Doe",
  "email": "john@example.com"
}
// Le client doit SAVOIR que pour modifier, c'est PUT /users/42
// Et pour supprimer, c'est DELETE /users/42

// Avec HATEOAS (ce que presque personne ne fait)
{
  "id": 42,
  "name": "John Doe",
  "email": "john@example.com",
  "_links": {
    "self": { "href": "/users/42", "method": "GET" },
    "update": { "href": "/users/42", "method": "PUT" },
    "delete": { "href": "/users/42", "method": "DELETE" },
    "orders": { "href": "/users/42/orders", "method": "GET" }
  }
}
// Le client decouvre les actions possibles via la reponse

Pourquoi personne ne le fait ? Parce que c'est du travail supplementaire cote serveur, ca augmente la taille des reponses, et en pratique les clients sont ecrits par la meme equipe que le serveur -- ils connaissent deja les URL.

HATEOAS fait sens dans un contexte ou l'API est consommee par des clients inconnus, ou les URL changent, ou la navigation est dynamique. Pour une API interne entre ton frontend React et ton backend, c'est du sur-engineering.

L'idempotence : le concept qui sauve des nuits

Si tu ne retiens qu'une chose de cet article, retiens l'idempotence. Une operation est idempotente si l'appeler 1 fois ou 100 fois produit le meme resultat.

Verbe HTTPIdempotentSafeUsage
GETOuiOuiLire une ressource
HEADOuiOuiVerifier l'existence
PUTOuiNonRemplacer une ressource
DELETEOuiNonSupprimer une ressource
POSTNonNonCreer une ressource
PATCHNon*NonModifier partiellement

*PATCH peut etre idempotent selon l'implementation, mais ce n'est pas garanti.

Pourquoi c'est important ? Parce que les reseaux sont peu fiables. Un timeout ne signifie pas que la requete a echoue -- peut-etre que le serveur l'a traitee mais que la reponse s'est perdue. Avec un GET ou un PUT, tu peux retry sans risque. Avec un POST, tu risques de creer un doublon.

Client: POST /orders {item: "widget", qty: 3}
Serveur: 201 Created (mais la reponse se perd)
Client: "Timeout... je reessaie"
Client: POST /orders {item: "widget", qty: 3}
Serveur: 201 Created -> DOUBLON !

La solution classique : ajouter un Idempotency-Key header au POST. Le serveur detecte les retries et renvoie la meme reponse sans re-executer l'operation. Stripe fait ca depuis des annees, et c'est une best practice que tout le monde devrait adopter.

Les status codes qui comptent

HTTP definit des dizaines de status codes. En pratique, tu en utilises 10-12. Voici ceux qui comptent vraiment :

Succes :

  • 200 OK : la requete a reussi, voici la reponse
  • 201 Created : la ressource a ete creee (avec le header Location)
  • 204 No Content : succes, mais rien a renvoyer (DELETE, PUT sans body de retour)

Erreur client :

  • 400 Bad Request : la requete est mal formee (JSON invalide, champ manquant)
  • 401 Unauthorized : pas authentifie (mauvais token, pas de token)
  • 403 Forbidden : authentifie mais pas autorise (tu n'as pas le droit)
  • 404 Not Found : la ressource n'existe pas
  • 409 Conflict : conflit avec l'etat actuel (version obsolete, doublon)
  • 422 Unprocessable Entity : le JSON est valide mais les donnees ne passent pas la validation metier

Erreur serveur :

  • 500 Internal Server Error : le serveur a plante (c'est ta faute, dev)

La distinction entre 400, 422 et 409 est souvent floue. Ma regle : 400 pour les erreurs de format, 422 pour les erreurs de validation metier, 409 pour les conflits d'etat. Si tu ne veux pas te prendre la tete, utilise 400 pour toutes les erreurs client et c'est acceptable.

REST vs GraphQL vs gRPC

La question revient dans chaque projet : quel protocole d'API choisir ?

REST (JSON over HTTP)

  • Pour : simple, universel, cacheable, outillage mature
  • Contre : over-fetching, under-fetching, pas de schema type
  • Quand : API publiques, CRUD simple, equipes heterogenes

GraphQL

  • Pour : le client demande exactement ce qu'il veut, un seul endpoint, schema type
  • Contre : complexite serveur, difficulte de caching, N+1 facile a creer
  • Quand : frontends complexes avec des besoins de donnees variables, mobile

gRPC

  • Pour : performance (Protocol Buffers), streaming bidirectionnel, types forts
  • Contre : pas browser-friendly, tooling moins mature, debug plus difficile
  • Quand : communication service-a-service, latence critique, microservices internes

Mon experience : REST pour 80% des projets, GraphQL quand le frontend est complexe et les donnees sont relationnelles, gRPC uniquement pour de la communication inter-services haute performance.

La verite inconfortable

90% des "API REST" dans le monde reel ne sont pas RESTful au sens de Fielding. Elles sont stateless (plus ou moins), utilisent les verbes HTTP (plus ou moins), renvoient du JSON, et c'est tout. Pas de HATEOAS, pas de cache headers systematiques, pas d'uniform interface stricte.

Et c'est parfaitement OK.

Le purisme REST est un piege. Implementer HATEOAS pour une API interne, c'est du temps perdu. Utiliser les 6 contraintes de Fielding pour un CRUD simple, c'est du sur-engineering. Ce qui compte, c'est :

  1. Des URL previsibles et coherentes
  2. Les bons verbes HTTP pour les bonnes operations
  3. Des status codes qui ont du sens
  4. Un format de reponse consistant
  5. Une documentation claire

Si tu fais ces 5 choses, ton API sera meilleure que 90% de ce qui existe. Tu pourras l'appeler "REST" sans que personne ne te corrige -- et si quelqu'un te corrige, c'est probablement quelqu'un qui n'a jamais lu la these de Fielding non plus.

Ce que Fielding penserait en 2025

Roy Fielding a dit dans une interview qu'il regrettait que le terme "REST" ait ete coopte pour decrire n'importe quelle API HTTP. Il avait raison. Mais le web a evolue, et les contraintes REST se sont adaptees aux realites du terrain.

Le vrai heritage de REST, ce n'est pas HATEOAS ou le statelessness strict. C'est l'idee que le web est une architecture, pas un accident. Que les protocoles ont un sens. Que les contraintes existent pour des raisons. Comprendre ces raisons, meme si tu ne les appliques pas toutes, fait de toi un meilleur architecte d'API.

Et ca, ca n'a pas change depuis 2000.

Ressources

Partager: