Les noms, c'est fait. Place aux actions.
Leçon 2, tu as appris à nommer : /livres, /livres/42, /livres/42/emprunts. Des noms, jamais des verbes. La règle tenait en une phrase : les URI nomment les choses, les verbes disent ce qu'on en fait.
Sauf qu'on a laissé la moitié du travail de côté. Tu sais désigner le livre 42. Mais le créer, le lire, le mettre à jour, le supprimer ? Ça, ce n'est pas l'URI qui le porte : c'est le verbe HTTP. La même adresse /livres/42 peut être lue, modifiée ou détruite selon le verbe que tu choisis. C'est ça, le génie de REST : une poignée de verbes, et tout le CRUD tient debout.
Le cours HTTP t'a déjà montré GET, POST, PUT, DELETE et le mot « idempotent » en deux phrases. Ici, on ne refait pas la base : on l'attaque côté design d'API. Pourquoi ces verbes, pas d'autres ? Pourquoi l'idempotence n'est pas un détail de puriste mais ce qui empêche d'emprunter deux fois le même livre quand le réseau hoquette ? Et où passe la frontière exacte entre PUT et PATCH ?
CRUD : quatre actions, cinq verbes
CRUD, c'est l'acronyme des quatre choses qu'on fait à n'importe quelle donnée : Create, Read, Update, Delete. Créer, lire, modifier, supprimer. REST mappe ces quatre actions sur les verbes HTTP, et la beauté du truc, c'est que l'URI ne bouge presque pas : c'est le verbe qui change de sens.
Reprends ta bibliothèque. Voici le mapping complet :
| Action CRUD | Verbe + URI | Ce que ça fait |
|---|---|---|
| Create | POST /livres |
Crée un nouveau livre dans la collection. C'est le serveur qui lui donne son id. |
| Read | GET /livresGET /livres/42 |
Lit la collection entière, ou un livre précis. Ne modifie rien. |
| Update | PUT /livres/42PATCH /livres/42 |
Met à jour le livre 42. PUT le remplace en entier, PATCH n'en change qu'une partie. |
| Delete | DELETE /livres/42 |
Supprime le livre 42 de la collection. |
Remarque le motif : pour créer, tu vises la collection (/livres), parce que tu n'as pas encore d'id. Pour tout le reste, tu vises un élément précis (/livres/42). Le verbe fait le reste du travail. Une seule famille d'adresses, cinq verbes, et tout le cycle de vie d'une donnée est couvert.
Safe et idempotent : deux mots qu'on confond tout le temps
Ces verbes ne se valent pas. La RFC 9110, le texte qui définit HTTP, les range selon deux propriétés. Comprendre ces deux mots, c'est comprendre pourquoi une API tient ou s'effondre sous la charge.
Safe (sûr) : le verbe ne modifie rien sur le serveur. C'est de la pure lecture. Les verbes safe sont GET, HEAD et OPTIONS. Tu peux les appeler mille fois, recharger la page en boucle, un robot peut tous les suivre : l'état du serveur ne bouge pas d'un poil.
Idempotent : appeler le verbe une fois ou cent fois laisse le serveur dans le même état final. Les verbes idempotents sont les safe (forcément) plus PUT et DELETE. Et POST n'est ni safe ni idempotent : chaque POST /livres crée un livre de plus.
Le piège du sens d'« idempotent ». Beaucoup croient qu'idempotent veut dire « renvoie la même réponse à chaque appel ». C'est faux. Idempotent parle de l'effet sur le serveur, pas de la réponse reçue. Exemple net : DELETE /livres/42 renvoie 200 la première fois (le livre existait, il est supprimé), puis 404 les fois suivantes (il n'existe plus). Les réponses diffèrent, et pourtant DELETE reste idempotent : après le premier appel, le livre 42 est parti, et il reste parti. L'état final est identique, quel que soit le nombre d'appels. C'est ça, et rien d'autre, l'idempotence.
Pourquoi ça compte vraiment : le réseau rejoue
L'idempotence n'est pas une coquetterie théorique. C'est ce qui te sauve le jour où le réseau fait des siennes, et le réseau fait toujours des siennes.
Voici la scène. Ton application mobile envoie une requête à l'API de la bibliothèque. Elle part, le serveur la traite... mais la réponse se perd en chemin : un wifi qui décroche, un tunnel, un timeout. Côté client, on ne sait pas si la requête a abouti ou non. Le réflexe universel des clients HTTP, c'est de réessayer. Ils rejouent la requête à l'identique.
Et là, tout dépend du verbe.
- Si c'était un PUT
/livres/42qui posait le titre « Dune » : le rejeu repose le même titre. Aucun dégât. Le livre 42 a le titre « Dune », qu'on l'écrive une ou trois fois. PUT est idempotent : réessayer est gratuit. - Si c'était un POST
/empruntspour emprunter le livre 42 : le premier POST a déjà enregistré l'emprunt, mais la réponse s'est perdue. Le client rejoue. Le serveur enregistre un second emprunt. Résultat : le même lecteur a emprunté le même livre deux fois, le stock affiche -1 exemplaire, et le support reçoit un ticket le lundi matin.
Tu vois la différence concrète ? Avec un verbe idempotent, le retry réseau est un non-événement. Avec POST, le retry est un piège. C'est pour ça qu'on choisit ses verbes avec soin, et qu'on protège les POST sensibles (on verra plus tard les clés d'idempotence, qui forcent un POST à se comporter proprement face au rejeu).
PUT contre PATCH : remplacer ou retoucher
Update a deux verbes, et la nuance entre eux fait trébucher beaucoup de monde. Elle est pourtant simple une fois posée.
PUT remplace la ressource entière. Tu envoies l'objet complet, et le serveur écrase le livre 42 avec exactement ce que tu donnes. Conséquence directe et souvent oubliée : un champ omis est un champ perdu. Si tu fais un PUT avec seulement {"titre": "Dune"}, tu viens d'effacer l'auteur, l'année, l'ISBN. PUT est idempotent : reposer le même objet complet redonne le même état.
PATCH ne modifie qu'une partie. Tu envoies seulement les champs qui changent, le reste est laissé intact. PATCH /livres/42 avec {"titre": "Dune"} change le titre et préserve l'auteur. C'est plus économe et moins risqué pour une petite retouche.
La nuance fine sur l'idempotence de PATCH. On lit souvent « PATCH n'est pas idempotent » : c'est vrai au sens de la RFC (HTTP ne le garantit pas), mais ça dépend de ce que tu mets dedans. Un PATCH qui dit « mets le titre à Dune » est idempotent en pratique : le rejouer redonne le même titre. En revanche, un PATCH qui dit « incrémente le compteur d'emprunts » ne l'est pas du tout : chaque appel ajoute +1, donc trois rejeux font +3. La leçon : avec PATCH, fais des opérations qui posent une valeur (« titre = X »), pas qui la déplacent (« +1 »), si tu veux survivre au retry réseau.
Tu envoies deux fois exactement la même requête PUT /livres/42 avec {"titre": "Dune"}. Puis tu envoies deux fois exactement la même requête POST /livres avec {"titre": "Dune"}. Dans chaque cas, dans quel état final se retrouve ta bibliothèque ?
Voir la réponse
PUT deux fois : un seul livre, à jour. Le premier PUT pose le titre « Dune » sur le livre 42 ; le second repose exactement le même titre. Rien n'a changé entre les deux états : PUT est idempotent. Tu as un livre 42 avec le titre « Dune ».
POST deux fois : deux livres créés. Chaque POST /livres crée une nouvelle ressource avec un nouvel id. Le premier crée le livre 42, le second crée le livre 43, tous deux intitulés « Dune ». POST n'est pas idempotent : tu te retrouves avec deux livres en doublon. C'est exactement le scénario que le retry réseau peut déclencher tout seul.
La matrice des verbes en un coup d'oeil
Garde cette matrice en tête. Trois colonnes : le verbe est-il safe (ne touche à rien) ? est-il idempotent (rejouer = sans danger) ? et quel effet il a sur ta bibliothèque ?
À toi : sens l'idempotence sous tes doigts
Un terminal simulé branché sur l'API de ta bibliothèque. Tu vas remplacer un livre avec PUT, rejouer la même commande pour voir que rien ne bouge, retoucher juste le titre avec PATCH, puis supprimer le livre et rejouer le DELETE. Regarde bien les réponses : elles changent, mais l'état final, lui, est stable.
The nouns are done. Now the actions.
In lesson 2 you learned to name things: /books, /books/42, /books/42/loans. Nouns, never verbs. The rule fit in one sentence: URIs name the things, verbs say what you do with them.
But we left half the job aside. You know how to point at book 42. But creating it, reading it, updating it, deleting it? That's not carried by the URI: it's carried by the HTTP verb. The same address /books/42 can be read, modified or destroyed depending on the verb you pick. That's the genius of REST: a handful of verbs, and the whole CRUD stands up.
The HTTP course already showed you GET, POST, PUT, DELETE and the word "idempotent" in two sentences. Here we don't redo the basics: we attack them from the API design angle. Why these verbs and not others? Why is idempotence not a purist's detail but the thing that stops you from borrowing the same book twice when the network hiccups? And where exactly is the line between PUT and PATCH?
CRUD: four actions, five verbs
CRUD is the acronym for the four things you do to any piece of data: Create, Read, Update, Delete. REST maps these four actions onto HTTP verbs, and the beauty of it is that the URI barely moves: it's the verb that changes meaning.
Back to your library. Here's the full mapping:
| CRUD action | Verb + URI | What it does |
|---|---|---|
| Create | POST /books |
Creates a new book in the collection. The server assigns its id. |
| Read | GET /booksGET /books/42 |
Reads the whole collection, or one precise book. Modifies nothing. |
| Update | PUT /books/42PATCH /books/42 |
Updates book 42. PUT replaces it entirely, PATCH changes only a part. |
| Delete | DELETE /books/42 |
Removes book 42 from the collection. |
Notice the pattern: to create, you target the collection (/books), because you don't have an id yet. For everything else, you target a precise item (/books/42). The verb does the rest. One family of addresses, five verbs, and the whole life cycle of a piece of data is covered.
Safe and idempotent: two words people mix up constantly
These verbs are not equal. RFC 9110, the text that defines HTTP, sorts them by two properties. Understanding these two words is understanding why an API holds up or collapses under load.
Safe: the verb modifies nothing on the server. It's pure reading. The safe verbs are GET, HEAD and OPTIONS. You can call them a thousand times, reload the page on a loop, a crawler can follow them all: the server's state doesn't move an inch.
Idempotent: calling the verb once or a hundred times leaves the server in the same final state. The idempotent verbs are the safe ones (necessarily) plus PUT and DELETE. And POST is neither safe nor idempotent: every POST /books creates one more book.
The trap in the meaning of "idempotent". Many believe idempotent means "returns the same response on every call". That's wrong. Idempotent is about the effect on the server, not the response you get back. Clear example: DELETE /books/42 returns 200 the first time (the book existed, it's deleted), then 404 the following times (it no longer exists). The responses differ, and yet DELETE stays idempotent: after the first call, book 42 is gone, and it stays gone. The final state is identical, whatever the number of calls. That, and nothing else, is idempotence.
Why it truly matters: the network replays
Idempotence isn't a theoretical nicety. It's what saves you the day the network acts up, and the network always acts up.
Here's the scene. Your mobile app sends a request to the library API. It leaves, the server processes it... but the response is lost on the way back: a dropped wifi, a tunnel, a timeout. On the client side, you don't know whether the request went through or not. The universal reflex of HTTP clients is to retry. They replay the request identically.
And there, everything depends on the verb.
- If it was a PUT
/books/42setting the title to "Dune": the replay sets the same title again. No harm. Book 42 has the title "Dune", whether you write it once or three times. PUT is idempotent: retrying is free. - If it was a POST
/loansto borrow book 42: the first POST already recorded the loan, but the response was lost. The client replays. The server records a second loan. Result: the same reader borrowed the same book twice, the stock shows -1 copy, and support gets a ticket on Monday morning.
See the concrete difference? With an idempotent verb, the network retry is a non-event. With POST, the retry is a trap. That's why you pick your verbs carefully, and why you protect sensitive POSTs (we'll see idempotency keys later, which force a POST to behave cleanly under replay).
PUT vs PATCH: replace or retouch
Update has two verbs, and the nuance between them trips up a lot of people. Yet it's simple once you lay it out.
PUT replaces the entire resource. You send the complete object, and the server overwrites book 42 with exactly what you give. Direct and often forgotten consequence: an omitted field is a lost field. If you PUT with only {"title": "Dune"}, you've just erased the author, the year, the ISBN. PUT is idempotent: re-sending the same complete object gives back the same state.
PATCH modifies only a part. You send only the fields that change, the rest is left intact. PATCH /books/42 with {"title": "Dune"} changes the title and preserves the author. It's leaner and less risky for a small edit.
The fine point on PATCH idempotence. You often read "PATCH is not idempotent": that's true per the RFC (HTTP doesn't guarantee it), but it depends on what you put in it. A PATCH that says "set the title to Dune" is idempotent in practice: replaying it gives back the same title. But a PATCH that says "increment the loan counter" is not idempotent at all: each call adds +1, so three replays make +3. The lesson: with PATCH, do operations that set a value ("title = X"), not ones that move it ("+1"), if you want to survive the network retry.
You send twice the exact same request PUT /books/42 with {"title": "Dune"}. Then you send twice the exact same request POST /books with {"title": "Dune"}. In each case, what final state is your library in?
Show the answer
PUT twice: a single book, up to date. The first PUT sets the title "Dune" on book 42; the second sets the very same title again. Nothing changed between the two states: PUT is idempotent. You have one book 42 with the title "Dune".
POST twice: two books created. Each POST /books creates a new resource with a new id. The first creates book 42, the second creates book 43, both titled "Dune". POST is not idempotent: you end up with two duplicate books. That's exactly the scenario a network retry can trigger on its own.
The verb matrix at a glance
Keep this matrix in mind. Three columns: is the verb safe (touches nothing)? is it idempotent (replay = harmless)? and what effect does it have on your library?
Your turn: feel idempotence under your fingers
A simulated terminal wired to your library API. You'll replace a book with PUT, replay the same command to see nothing moves, retouch just the title with PATCH, then delete the book and replay the DELETE. Watch the responses closely: they change, but the final state stays stable.
🎯 Pratique
S'entraîner (clique pour ouvrir) :
💬 Ré-explique sans regarder
Explique à un collègue la différence entre « safe » et « idempotent », et pourquoi un DELETE qui renvoie 200 puis 404 reste idempotent.
DELETE renvoie 200 puis 404 : les réponses diffèrent, mais après le premier appel le livre est parti et reste parti. L'état final est identique, donc DELETE est idempotent. POST, lui, n'est ni l'un ni l'autre.🧠 Rappel libre
Sans remonter : quelle est la différence entre PUT et PATCH sur /livres/42, et lequel risque de t'effacer l'auteur par accident ?
PUT remplace la ressource entière : un champ omis est perdu. Donc un PUT avec seulement {"titre":"Dune"} efface l'auteur. PATCH ne change que les champs envoyés et laisse le reste intact : il préserve l'auteur. Côté retry réseau, PUT est toujours idempotent ; PATCH l'est si l'opération pose une valeur (« titre = X »), pas si elle l'incrémente (« +1 »).⚖️ Juge le code de l'IA
Tu veux un endpoint pour marquer un livre comme lu. L'IA propose : « Facile, ajoute GET /livres/42/marquer-lu : un simple clic suffit à le déclencher. » Tu acceptes, ou tu rejettes ?
GET doit être safe : il ne doit RIEN modifier. Or « marquer comme lu » change l'état du serveur. Un crawler ou un préchargement de lien déclencherait alors la modification tout seul, sans clic humain. Ensuite, marquer-lu est un verbe dans l'URL : la leçon 2 l'interdit, l'URI nomme, le verbe HTTP agit. Le bon design : PATCH /livres/42 avec {"statut":"lu"}. Modifier un champ, c'est un PATCH, pas un GET sur une URL-verbe.DELETE /livres/42 renvoie 200 la première fois, puis 404 ensuite. Est-il idempotent ?POST /emprunts pour emprunter un livre, mais la réponse se perd. Il rejoue automatiquement la requête. Que se passe-t-il, et qu'est-ce qui l'aurait évité ?You know how to ask: POST to create, GET to read, PUT and PATCH to update, DELETE to delete. But what does a well-behaved API reply? Lesson 4: choosing the right status code. 201, 204, 400 vs 422, 409, and the anti-pattern of the 200 that lies by returning an error disguised as success.
Lesson 4: Choosing the right status code →