Les URI sont des noms. Maintenant, des noms de quoi ?
Leçon 1, on a posé la règle qui sépare REST du reste : les URI sont des noms, pas des verbes. /getLivres ment, parce que c'est un verbe déguisé en adresse. La bonne adresse, c'est /livres, et l'action vit ailleurs. Tu as retenu ça. Bien.
Mais une question reste en suspens : des noms de quoi, exactement ? Ta bibliothèque a des livres, des membres, des emprunts, des étagères, des amendes de retard. Tout ne mérite pas une adresse. Cette leçon répond à la vraie question d'un concepteur d'API : quelles choses de mon domaine deviennent des ressources, et à quelle adresse ?
Voici la métaphore qui va te guider. Une ressource, c'est une page que tu peux manipuler. Comme une fiche dans un classeur : chaque chose importante de ton domaine a sa fiche, et chaque fiche a son adresse. Modéliser une API, c'est dessiner ce classeur : décider quelles fiches existent, et comment on les range.
Modéliser la bibliothèque : collection et élément
Commençons par lister les choses importantes de notre bibliothèque. Trois sautent aux yeux : les livres, les membres, les emprunts. Chacune devient une ressource, donc une adresse.
Et là, une distinction qui revient partout dans les API : la collection et l'élément.
/api/livres: la collection. C'est l'étagère entière, tous les livres ensemble./api/livres/42: un élément. C'est le livre dont l'identifiant est 42, une fiche précise.
Le pluriel n'est pas un détail de style. /api/livres au pluriel dit « voici l'ensemble », et /api/livres/42 pioche un élément dedans. C'est cohérent et ça se lit tout seul. Trois règles d'écriture, suivies par la quasi-totalité des API sérieuses :
- Pluriel :
/livres, pas/livre. La collection d'abord, l'élément se déduit avec un identifiant. - Minuscules :
/livres, pas/Livres. Les URL sont sensibles à la casse, autant rester simple et prévisible. - Pas d'accents :
/emprunts, pas/empruntés. Les caractères accentués s'encodent en URL (%C3%A9) et deviennent illisibles. Reste en ASCII.
Toujours nommer une collection au pluriel, en minuscules, sans accent. /api/livres et /api/livres/42 forment un couple lisible : la liste, puis un élément de la liste. Un développeur qui voit /api/membres/7 comprend instantanément « le membre numéro 7 », sans documentation.
La hiérarchie : ranger une ressource dans une autre
Certaines choses appartiennent clairement à une autre. Les emprunts d'un livre, par exemple : ils n'existent pas tout seuls dans le vide, ils sont rattachés à ce livre. L'URL peut raconter cette appartenance :
/api/livres/42/emprunts
Lis-la de gauche à droite, comme un chemin : « dans la collection des livres, prends le livre 42, puis ses emprunts ». L'adresse devient une phrase. C'est ça, la hiérarchie : une ressource imbriquée sous celle à qui elle appartient.
Mais attention, c'est tentant d'en abuser. On pourrait écrire /api/livres/42/emprunts/8/membre/7/amendes/3, et plus personne ne comprend rien. Deux garde-fous simples :
- Imbrique quand l'appartenance est claire et forte. Les emprunts du livre 42 appartiennent au livre 42 :
/livres/42/empruntsa du sens. - N'imbrique pas au-delà de deux niveaux. Au-delà, l'URL devient illisible et fragile. Si tu veux un emprunt précis, donne-lui sa propre adresse à plat :
/api/emprunts/8. Un emprunt est une chose à part entière, il mérite sa fiche.
Le sur-imbriquement : au-delà de deux niveaux (collection/id/sous-collection), l'URL devient un labyrinthe. /api/livres/42/emprunts/8/membre est déjà trop : si l'emprunt 8 existe seul, vise /api/emprunts/8 et mets le membre dans sa réponse JSON. Règle pratique : imbrique pour lister les enfants d'un parent, mais donne une adresse à plat à chaque ressource qu'on veut viser directement.
Anti-pattern n°1 : le verbe dans l'URL
C'est l'erreur la plus fréquente, et celle de la leçon 1 vue de plus près. Une URL ne doit nommer que la chose. Le verbe, l'action, ça ne vit pas dans l'adresse : ça vit dans la méthode HTTP (on y arrive en leçon 3). Voici le tableau correctif à garder sous les yeux :
| URL qui sent mauvais | Ce qu'il faut écrire |
|---|---|
/getLivres | GET /livres |
/createLivre | POST /livres |
/deleteLivre?id=42 | DELETE /livres/42 |
/livres/list | GET /livres |
Regarde la colonne de droite : à chaque fois, l'URL ne contient plus qu'un nom (/livres, /livres/42), et l'action est passée dans le verbe HTTP (GET, POST, DELETE). C'est la grande idée de REST : une seule adresse, plusieurs actions selon la méthode. La leçon 3 t'apprendra exactement quel verbe fait quoi. Pour l'instant, retiens juste le réflexe : si tu vois un verbe dans une URL, c'est un signal d'alerte.
Tu vas appeler /api/livres/42/emprunts. À ton avis, ça te renvoie quoi : tous les emprunts de la bibliothèque, ou seulement ceux du livre 42 ?
Voir la réponse
Seulement les emprunts du livre 42. L'URL se lit comme un chemin : « le livre 42, puis ses emprunts ». Le 42 au milieu restreint la portée. Pour avoir tous les emprunts de la bibliothèque, tu viserais la collection à plat /api/emprunts. C'est toute la force de la hiérarchie : l'adresse dit elle-même de quoi on parle.
La query string filtre, elle ne nomme pas une chose
Encore une confusion classique. Tu veux les livres disponibles. La tentation, c'est d'inventer une nouvelle adresse : /api/livres-disponibles. Mauvaise piste. « Disponible », ce n'est pas une autre chose : c'est le même ensemble de livres, filtré. La bonne écriture passe par la query string :
/api/livres?statut=disponible
Souviens-toi du cours HTTP, leçon sur l'anatomie d'une URL : tout ce qui suit le ? est la query string, une liste de paramètres clé=valeur. Ici, statut=disponible ne crée pas une ressource ; il affine la collection /api/livres. C'est exactement le bon outil :
- Le chemin (
/api/livres) nomme la chose : la collection des livres. - La query string (
?statut=disponible) la trie, la filtre, la pagine. Elle ne change pas quelle chose on regarde, seulement comment.
On creusera la pagination, les filtres et le tri en détail en leçon 6 (?page=2, ?sort=-date...). Pour l'instant, grave la frontière : le chemin nomme, la query string affine. Inventer /livres-disponibles, c'est confondre les deux.
L'arbre des ressources de la bibliothèque
Voici notre classeur, vu de haut. Chaque boîte est une adresse. Lis les branches comme des chemins : on part de /api, on descend vers les collections, puis vers un élément précis, puis vers ses ressources imbriquées.
À toi : navigue l'API comme une arborescence
Un terminal simulé. Tu vas interroger l'API de la bibliothèque avec curl, en descendant l'arbre : d'abord la collection, puis un élément, puis ses emprunts. Observe comme l'URL guide la réponse.
Exercice : corrige ces 5 URL
Voici cinq adresses qui sentent mauvais, vraies du genre qu'on croise dans des API mal conçues. Pour chacune, réécris-la proprement avant d'ouvrir la réponse. Pense « nom, pas verbe », « chemin pour nommer, query string pour filtrer ».
🔧 1. GET /api/getAllLivres
GET /api/livres. Le verbe get est déjà porté par la méthode HTTP, et All est redondant : une collection au pluriel veut déjà dire « tous ».
🔧 2. POST /api/livre/create
POST /api/livres. On crée dans la collection (pluriel), et le verbe create disparaît : c'est le POST qui dit « crée ».
🔧 3. GET /api/livres/42/delete
DELETE /api/livres/42. Supprimer n'est pas un segment d'URL : c'est la méthode DELETE sur l'adresse de l'élément.
🔧 4. GET /api/livresDisponibles
GET /api/livres?statut=disponible. « Disponible » n'est pas une autre chose : c'est la collection des livres, filtrée. Le filtre va dans la query string.
🔧 5. GET /api/Membres/7/Emprunts
GET /api/membres/7/emprunts. Tout en minuscules. La structure était bonne (les emprunts du membre 7), seule la casse clochait.
URIs are nouns. Now, nouns of what?
In lesson 1 we set the rule that separates REST from the rest: URIs are nouns, not verbs. /getBooks lies, because it's a verb dressed up as an address. The right address is /books, and the action lives elsewhere. You got that. Good.
But one question stays open: nouns of what, exactly? Your library has books, members, loans, shelves, late fees. Not everything deserves an address. This lesson answers the real question an API designer asks: which things in my domain become resources, and at what address?
Here's the metaphor that will guide you. A resource is a page you can manipulate. Like a card in a filing cabinet: every important thing in your domain has its card, and every card has its address. Modelling an API is drawing that cabinet: deciding which cards exist, and how you file them.
Modelling the library: collection and element
Let's start by listing the important things in our library. Three jump out: books, members, loans. Each becomes a resource, so an address.
And here a distinction that shows up everywhere in APIs: the collection and the element.
/api/books: the collection. It's the whole shelf, all the books together./api/books/42: one element. It's the book whose id is 42, one precise card.
The plural isn't a style detail. /api/books in the plural says "here's the set", and /api/books/42 picks one element from it. It's consistent and reads by itself. Three writing rules, followed by nearly every serious API:
- Plural:
/books, not/book. The collection first, the element follows with an id. - Lowercase:
/books, not/Books. URLs are case-sensitive, so keep it simple and predictable. - No accents:
/loans, not accented words. Accented characters get URL-encoded (%C3%A9) and become unreadable. Stay in ASCII.
Always name a collection in the plural, lowercase, no accents. /api/books and /api/books/42 form a readable pair: the list, then an element of the list. A developer who sees /api/members/7 instantly understands "member number 7", with no documentation.
Hierarchy: filing one resource inside another
Some things clearly belong to another. A book's loans, for instance: they don't exist on their own in a vacuum, they're tied to this book. The URL can tell that story of belonging:
/api/books/42/loans
Read it left to right, like a path: "in the books collection, take book 42, then its loans". The address becomes a sentence. That's hierarchy: a resource nested under the one it belongs to.
But beware, it's tempting to overdo it. You could write /api/books/42/loans/8/member/7/fines/3, and nobody understands a thing anymore. Two simple guardrails:
- Nest when belonging is clear and strong. Book 42's loans belong to book 42:
/books/42/loansmakes sense. - Don't nest beyond two levels. Past that, the URL becomes unreadable and fragile. If you want a precise loan, give it its own flat address:
/api/loans/8. A loan is a thing in its own right, it deserves its own card.
Beware of over-nesting: beyond two levels (collection/id/sub-collection), the URL becomes a maze. /api/books/42/loans/8/member is already too much: if loan 8 exists on its own, target /api/loans/8 and put the member in its JSON response. Rule of thumb: nest to list a parent's children, but give a flat address to every resource you want to target directly.
Anti-pattern #1: the verb in the URL
It's the most common mistake, and lesson 1's lie seen up close. A URL must name only the thing. The verb, the action, doesn't live in the address: it lives in the HTTP method (we get there in lesson 3). Here's the fix-it table to keep in sight:
| URL that smells | What to write |
|---|---|
/getBooks | GET /books |
/createBook | POST /books |
/deleteBook?id=42 | DELETE /books/42 |
/books/list | GET /books |
Look at the right column: each time, the URL holds only a noun (/books, /books/42), and the action moved into the HTTP verb (GET, POST, DELETE). That's REST's big idea: one address, several actions depending on the method. Lesson 3 will teach you exactly which verb does what. For now, just keep the reflex: if you see a verb in a URL, it's a warning sign.
You're about to call /api/books/42/loans. What do you think it returns: all the library's loans, or only those of book 42?
Show the answer
Only book 42's loans. The URL reads like a path: "book 42, then its loans". The 42 in the middle narrows the scope. To get all the library's loans, you'd target the flat collection /api/loans. That's the whole strength of hierarchy: the address itself says what we're talking about.
The query string filters, it doesn't name a thing
Another classic mix-up. You want the available books. The temptation is to invent a new address: /api/available-books. Wrong track. "Available" isn't another thing: it's the same set of books, filtered. The right way goes through the query string:
/api/books?status=available
Remember the HTTP course, the lesson on URL anatomy: everything after the ? is the query string, a list of key=value parameters. Here, status=available doesn't create a resource; it refines the /api/books collection. It's exactly the right tool:
- The path (
/api/books) names the thing: the books collection. - The query string (
?status=available) sorts it, filters it, paginates it. It doesn't change which thing we look at, only how.
We'll dig into pagination, filters and sorting in detail in lesson 6 (?page=2, ?sort=-date...). For now, carve in the boundary: the path names, the query string refines. Inventing /available-books confuses the two.
The library's resource tree
Here's our cabinet, seen from above. Each box is an address. Read the branches as paths: you start from /api, descend to collections, then to a precise element, then to its nested resources.
Your turn: navigate the API like a tree
A simulated terminal. You'll query the library API with curl, walking down the tree: first the collection, then an element, then its loans. Watch how the URL drives the response.
Exercise: fix these 5 URLs
Here are five addresses that smell, the real kind you meet in badly designed APIs. For each one, rewrite it cleanly before opening the answer. Think "noun, not verb", "path to name, query string to filter".
🔧 1. GET /api/getAllBooks
GET /api/books. The verb get is already carried by the HTTP method, and All is redundant: a plural collection already means "all".
🔧 2. POST /api/book/create
POST /api/books. You create in the collection (plural), and the verb create disappears: it's the POST that says "create".
🔧 3. GET /api/books/42/delete
DELETE /api/books/42. Deleting isn't a URL segment: it's the DELETE method on the element's address.
🔧 4. GET /api/availableBooks
GET /api/books?status=available. "Available" isn't another thing: it's the books collection, filtered. The filter goes in the query string.
🔧 5. GET /api/Members/7/Loans
GET /api/members/7/loans. All lowercase. The structure was right (member 7's loans), only the case was off.
🎯 Pratique
S'entraîner (clique pour ouvrir) :
💬 Ré-explique sans regarder
Explique à un collègue la différence entre une collection et un élément, et pourquoi ?statut=disponible n'est pas une nouvelle ressource.
/api/livres est la collection (l'étagère entière), /api/livres/42 est un élément (une fiche). Le chemin nomme la chose ; la query string affine la même chose (filtre, tri, pagination). ?statut=disponible renvoie les mêmes livres, filtrés : ce n'est pas une autre ressource, donc surtout pas /livres-disponibles.🧠 Rappel libre
Sans remonter : cite les trois règles d'écriture d'une URI de ressource, et la limite à respecter pour l'imbrication.
/livres), minuscules (/livres, pas /Livres), pas d'accents (ASCII, sinon ça s'encode en %C3%A9). La limite d'imbrication : deux niveaux maximum (collection/id/sous-collection). Au-delà, donne une adresse à plat à la ressource (/api/emprunts/8).⚖️ Juge le code de l'IA
Tu veux une route pour emprunter le livre 42. L'IA propose : « Voilà, j'ai créé GET /api/livres/emprunter?id=42, c'est clair et ça marche. » Tu acceptes, ou tu rejettes ?
emprunter est un verbe dans l'URL : l'action ne vit jamais dans l'adresse. 2. L'identifiant du livre est balancé dans la query string alors qu'il fait partie du chemin de la ressource. Le bon design : emprunter, c'est créer un emprunt, donc POST /api/livres/42/emprunts (on crée un emprunt sous le livre 42). L'action est portée par la méthode, l'URL ne nomme que des choses./api/livres/42/emprunts/8/membre/7/amendes est-il un mauvais design ?Tes URI nomment les choses. Mais qui dit quoi en FAIRE ? Leçon 3 : GET, POST, PUT, PATCH, DELETE, et l'idempotence qui sauve quand le réseau rejoue.
Leçon 3 : Les verbes HTTP : le CRUD →