Chapitre 2 - Les principes REST

Les 6 contraintes REST

REST (REpresentational State Transfer) n'est ni un protocole ni une norme : c'est un style d'architecture, décrit par Roy Fielding en 2000. Une API est dite RESTful quand elle respecte ses contraintes.

Pour les illustrer, on reprend l'analogie du restaurant, et l'API publique de GitHub.

REST est décrit par 6 contraintes : article Wikipédia.

1 - Client-serveur

Le client (l'interface) et le serveur (les données, la logique) sont séparés et évoluent indépendamment. Tant que le contrat est respecté, on peut refaire tout le front sans toucher au back, ou l'inverse.

Au restaurant : vous ne passez pas en cuisine. Le chef peut changer de four sans que vous changiez votre façon de commander.

2 - Sans état (stateless)

Chaque requête contient tout ce qu'il faut pour être traitée. Le serveur ne garde aucun contexte de session entre deux requêtes.

curl -H "Authorization: Bearer mon-token" https://api.github.com/user

Au restaurant : le serveur a une mémoire de poisson rouge. Rappelez-lui votre numéro de table à chaque commande.

Avantage : n'importe quel serveur peut répondre à n'importe quelle requête, ce qui facilite la montée en charge.

3 - Cache

Les réponses indiquent si elles peuvent être mises en cache, et combien de temps. Le client (ou un proxy) peut alors réutiliser une réponse sans redemander.

curl -i https://api.github.com/users/thibaudfrichet
HTTP/2 200
cache-control: public, max-age=60, s-maxage=60

Ici, GitHub autorise à garder la réponse 60 secondes.

Au restaurant : la carte ne change pas toutes les 5 minutes. Pas besoin de la redemander à chaque fois.

4 - Interface uniforme

La contrainte centrale de REST. Toutes les ressources se manipulent de la même manière :

Conséquence : quand on connaît une API REST, on sait déjà à moitié utiliser toutes les autres.

5 - Système en couches

Le client ne sait pas (et n'a pas besoin de savoir) s'il parle directement au serveur ou à un intermédiaire : reverse proxy, load balancer, CDN, cache, passerelle d'API…

6 - Code à la demande (facultatif)

Le serveur peut envoyer du code exécutable au client, typiquement du JavaScript. C'est la seule contrainte facultative, et la moins utilisée dans les API.

La plupart des API qui se disent « REST » n'en respectent pas toutes les contraintes, en particulier HATEOAS. Fielding s'en plaint publiquement depuis 2008. Dans la pratique, « API REST » veut souvent dire « API HTTP + JSON avec des URL propres ».

Ressources, URI et représentations

Les ressources

Une ressource est tout ce qui mérite d'avoir un nom et une adresse : un utilisateur, un dépôt, un livre, un emprunt, le profil de l'utilisateur connecté…

Une ressource n'est pas une table SQL. /user sur GitHub est une ressource (« moi »), alors qu'elle correspond à une ligne parmi d'autres de la table des utilisateurs. À l'inverse, un emprunt de livre peut réunir des données de plusieurs tables.

Les URI

Chaque ressource est identifiée par une URI (Uniform Resource Identifier). L'API de GitHub en donne de bons exemples :

Type Exemple Signification
Collection /users Tous les utilisateurs
Élément /users/thibaudfrichet L'utilisateur thibaudfrichet
Sous-collection /users/thibaudfrichet/repos Les dépôts de thibaudfrichet
Singleton /user L'utilisateur connecté (il n'y en a qu'un)

L'URI désigne quoi. C'est la méthode HTTP qui dit quoi faire avec. Les règles de nommage sont détaillées au chapitre 5.

GET    /v1/livres/3              ✅ lire le livre 3
DELETE /v1/livres/3              ✅ supprimer le livre 3
GET    /v1/deleteLivre?id=3      ❌ un verbe dans l'URL

Les représentations

Le client ne reçoit jamais la ressource elle-même, mais une représentation de son état à un instant donné. Une même ressource peut avoir plusieurs représentations.

Le client indique ce qu'il veut avec le header Accept. C'est la négociation de contenu. Exemple avec l'API d'une bibliothèque :

En JSON :

curl -H "Accept: application/json" https://api.biblio.example/v1/livres/3
{"id": 3, "titre": "Le Petit Prince", "auteur": "Antoine de Saint-Exupéry"}

En XML :

curl -H "Accept: application/xml" https://api.biblio.example/v1/livres/3
<livre id="3">
  <titre>Le Petit Prince</titre>
  <auteur>Antoine de Saint-Exupéry</auteur>
</livre>

Et si le format demandé n'existe pas, le serveur répond 406 Not Acceptable :

curl -i -H "Accept: image/png" https://api.biblio.example/v1/livres/3
HTTP/1.1 406 Not Acceptable

Une représentation, c'est comme une photo : la même personne peut être en couleur ou en noir et blanc, en portrait ou en miniature. La personne, elle, ne change pas.

Dans l'autre sens, c'est le header Content-Type qui indique le format du body que le client envoie.

Les verbes HTTP

Les verbes (ou méthodes) HTTP indiquent l'action à effectuer sur la ressource désignée par l'URI.

Tous les exemples de cette page fonctionnent avec JSONPlaceholder, une fausse API de blog : essayez-les avec curl -i !

JSONPlaceholder fait semblant : il répond comme si l'opération avait réussi, mais rien n'est réellement créé ni supprimé. Parfait pour s'entraîner sans rien casser.

Les verbes à connaître

Verbe Rôle Exemple Body
GET Lire GET /posts Non
POST Créer dans une collection, ou déclencher un traitement POST /posts Oui
PUT Remplacer entièrement (ou créer à une URI connue) PUT /posts/1 Oui
PATCH Modifier partiellement PATCH /posts/1 Oui
DELETE Supprimer DELETE /posts/1 Non
HEAD Comme GET, mais seulement les headers HEAD /posts Non
OPTIONS Lister les méthodes autorisées OPTIONS /posts/1 Non
curl -i https://jsonplaceholder.typicode.com/posts/1
curl -i -X DELETE https://jsonplaceholder.typicode.com/posts/1
curl -I https://jsonplaceholder.typicode.com/posts     # -I envoie un HEAD
curl -i -X OPTIONS https://jsonplaceholder.typicode.com/posts/1

En principe, OPTIONS renvoie la liste des méthodes dans le header Allow. JSONPlaceholder la renvoie dans Access-Control-Allow-Methods, un header lié à CORS (voir chapitre 8).

Sûr et réexécutable

Verbe Sûr Réexécutable
GET, HEAD, OPTIONS ✅ ✅
PUT ❌ ✅
DELETE ❌ ✅
POST ❌ ❌
PATCH ❌ ❌ (pas garanti)

Cliquer 3 fois sur « Payer » envoie 3 POST : 3 paiements (et un client furieux).
Supprimer 3 fois l'article 1 avec DELETE, c'est toujours un seul article supprimé.

Réexécutable ne veut pas dire « même réponse » : le premier DELETE renvoie 204, le second peut renvoyer 404. Ce qui compte, c'est l'état du serveur.

PUT ou PATCH ?

PUT remplace toute la ressource : les champs absents sont perdus.

curl -i -X PUT https://jsonplaceholder.typicode.com/posts/1 \
  -H "Content-Type: application/json" \
  -d '{"id": 1, "userId": 1, "title": "Nouveau titre", "body": "Nouveau contenu"}'

PATCH ne modifie que les champs envoyés.

curl -i -X PATCH https://jsonplaceholder.typicode.com/posts/1 \
  -H "Content-Type: application/json" \
  -d '{"title": "Nouveau titre"}'

Avec un PUT qui ne contiendrait que {"title": "Nouveau titre"}, l'article perdrait son contenu.

Ne jamais modifier avec GET

Un GET doit rester sûr. Les navigateurs, les robots d'indexation et les outils de préchargement suivent les liens sans demander la permission.

❌ GET    /api/article/delete?id=1
✅ DELETE /posts/1

En 2005, le Google Web Accelerator préchargeait les liens des pages visitées… y compris les liens « supprimer » de certaines interfaces d'administration.
https://www.lemondeinformatique.fr/actualites/lire-retour-au-stand-pour-google-web-accelerator-8289.html

Les codes de statut

Le code de statut est la première chose qu'un client regarde. Un bon code évite au client de devoir lire le body pour savoir si ça a marché.

Une API qui répond toujours 200 OK avec {"error": true} dans le body oblige chaque client à réinventer la gestion d'erreurs. Le code de statut est fait pour ça : utilisez-le.

Les exemples ci-dessous concernent l'API d'une bibliothèque en ligne : des livres, des membres, des emprunts.

2xx - Succès

CodeQuand l'utiliserExemple
200 OKSuccès avec un bodyGET /v1/livres
201 CreatedRessource créée. Ajouter le header LocationPOST /v1/emprunts
204 No ContentSuccès sans bodyDELETE /v1/emprunts/42

3xx - Redirection

CodeQuand l'utiliser
301 Moved PermanentlyLa ressource a changé d'adresse définitivement
304 Not ModifiedLa version en cache du client est toujours bonne

4xx - Erreur du client

CodeQuand l'utiliserExemple
400 Bad RequestRequête mal forméeUn body qui n'est pas du JSON valide
401 UnauthorizedPas authentifié, ou authentification invalideGET /v1/me sans token
403 ForbiddenAuthentifié, mais pas autoriséUn membre qui veut supprimer un livre du catalogue
404 Not FoundLa ressource n'existe pasGET /v1/livres/999999
405 Method Not AllowedMéthode non supportée sur cette URI. Ajouter le header AllowDELETE /v1/livres (supprimer tout le catalogue)
406 Not AcceptableFormat demandé dans Accept indisponibleAccept: image/png
409 ConflictConflit avec l'état actuel de la ressourceEmprunter un livre déjà emprunté
418 I'm a teapotLe serveur est une théière (RFC 2324, poisson d'avril de 1998)Demander un café à une théière
422 Unprocessable ContentRequête bien formée mais données invalidesUne date de retour antérieure à la date d'emprunt
429 Too Many RequestsTrop de requêtes. Ajouter le header Retry-AfterUn script qui interroge l'API en boucle

5xx - Erreur du serveur

CodeQuand l'utiliser
500 Internal Server ErrorBug côté serveur. Ne jamais le renvoyer volontairement
502 Bad GatewayUn intermédiaire n'a pas obtenu de réponse valide du serveur
503 Service UnavailableServeur indisponible (maintenance, surcharge)

Les pièges classiques

La liste complète est sur MDN. Et pour les retenir : http.cat.

Le modèle de maturité de Richardson

En 2008, Leonard Richardson propose un modèle en 4 niveaux pour mesurer à quel point une API exploite le web. Chaque niveau ajoute un des principes REST.

Pyramide du modèle de maturité de Richardson

Fil conducteur : dans l'API d'une bibliothèque, annuler la réservation 42.

Niveau 0 - Le marécage du POX (Plain Old XML)

Une seule URL, une seule méthode (POST), et l'action est décrite dans le body. HTTP ne sert que de tuyau. C'est le cas de SOAP et de XML-RPC (POX : Plain Old XML).

POST /api
Content-Type: application/json

{"action": "cancelReservation", "reservationId": 42}
HTTP/1.1 200 OK

{"success": false, "error": "Livre déjà retiré"}

Niveau 1 - Les ressources

Chaque ressource a sa propre URL, mais on utilise encore POST pour tout, avec des verbes dans les URL.

POST /api/reservations/42/cancel

Niveau 2 - Les verbes et codes HTTP

On utilise la bonne méthode HTTP pour chaque action, et les bons codes de statut. C'est le niveau de la grande majorité des API « REST ».

DELETE /v1/reservations/42
HTTP/1.1 409 Conflict

Niveau 3 - Les contrôles hypermédias

Les réponses contiennent des liens vers les actions possibles. Le client n'a plus besoin de deviner les URL : il suit les liens. C'est HATEOAS.

{
  "id": 42,
  "status": "waiting",
  "_links": {
    "self":   {"href": "/v1/reservations/42"},
    "cancel": {"href": "/v1/reservations/42", "method": "DELETE"},
    "book":   {"href": "/v1/livres/3"}
  }
}

Si le livre a déjà été retiré, le lien cancel n'apparaît plus : le client sait qu'il ne peut plus annuler, sans connaître les règles métier.

Selon Fielding, seul le niveau 3 est vraiment REST. On y revient en détail au chapitre 9.