Chapitre 2 - Les principes REST
- Les 6 contraintes REST
- Ressources, URI et représentations
- Les verbes HTTP
- Les codes de statut
- Le modèle de maturité de Richardson
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 :
- chaque ressource est identifiée par une URI :
/users/thibaudfrichet,/repos/thibaudfrichet/homelab-agent; - on la manipule à travers ses représentations (JSON, XML…) ;
- les messages sont autodescriptifs : méthode,
Content-Type, code de statut ; - les réponses contiennent des liens vers les actions possibles : c'est HATEOAS, vu au chapitre 9.
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
- Sûr (safe) : la requête ne modifie rien sur le serveur. On peut l'envoyer sans risque.
- Réexécutable : envoyer la requête 1 fois ou 10 fois laisse le serveur dans le même état. On peut la renvoyer sans risque si le réseau a coupé.
| 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
| Code | Quand l'utiliser | Exemple |
|---|---|---|
200 OK | Succès avec un body | GET /v1/livres |
201 Created | Ressource créée. Ajouter le header Location | POST /v1/emprunts |
204 No Content | Succès sans body | DELETE /v1/emprunts/42 |
3xx - Redirection
| Code | Quand l'utiliser |
|---|---|
301 Moved Permanently | La ressource a changé d'adresse définitivement |
304 Not Modified | La version en cache du client est toujours bonne |
4xx - Erreur du client
| Code | Quand l'utiliser | Exemple |
|---|---|---|
400 Bad Request | Requête mal formée | Un body qui n'est pas du JSON valide |
401 Unauthorized | Pas authentifié, ou authentification invalide | GET /v1/me sans token |
403 Forbidden | Authentifié, mais pas autorisé | Un membre qui veut supprimer un livre du catalogue |
404 Not Found | La ressource n'existe pas | GET /v1/livres/999999 |
405 Method Not Allowed | Méthode non supportée sur cette URI. Ajouter le header Allow | DELETE /v1/livres (supprimer tout le catalogue) |
406 Not Acceptable | Format demandé dans Accept indisponible | Accept: image/png |
409 Conflict | Conflit avec l'état actuel de la ressource | Emprunter un livre déjà emprunté |
418 I'm a teapot | Le serveur est une théière (RFC 2324, poisson d'avril de 1998) | Demander un café à une théière |
422 Unprocessable Content | Requête bien formée mais données invalides | Une date de retour antérieure à la date d'emprunt |
429 Too Many Requests | Trop de requêtes. Ajouter le header Retry-After | Un script qui interroge l'API en boucle |
5xx - Erreur du serveur
| Code | Quand l'utiliser |
|---|---|
500 Internal Server Error | Bug côté serveur. Ne jamais le renvoyer volontairement |
502 Bad Gateway | Un intermédiaire n'a pas obtenu de réponse valide du serveur |
503 Service Unavailable | Serveur indisponible (maintenance, surcharge) |
Les pièges classiques
- 401 ou 403 ?
401: « je ne sais pas qui tu es ».403: « je sais qui tu es, et c'est non ». - 400 ou 422 ?
400: je n'arrive pas à lire ta requête.422: je l'ai lue, mais son contenu n'est pas valide. FastAPI renvoie422par défaut pour les erreurs de validation. - 404 ou 403 ? Pour une ressource qui appartient à quelqu'un d'autre, répondre
404évite de révéler qu'elle existe. On en reparle au chapitre 8.
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.
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.