Clarifier le périmètre d’une API REST et le vocabulaire
Ressource, collection, représentation
Une API REST expose des ressources (des “objets métier” identifiables) via des URI stables. Une collection est un ensemble de ressources du même type. Une représentation est la forme sérialisée d’une ressource (souvent JSON) renvoyée au client.
- Ressource : un élément adressable, ex.
/users/42. - Collection : un ensemble, ex.
/users. - Représentation : le JSON renvoyé pour
/users/42(peut varier selon le contexte, ex. “résumé” vs “détail”).
Le périmètre d’une API REST se définit en listant ce que l’API doit exposer (ressources et relations) et ce qu’elle ne doit pas exposer (détails internes, données sensibles, opérations techniques). Le vocabulaire partagé (ressource/collection/représentation) évite de dériver vers des endpoints “fourre-tout” centrés sur des actions.
Identifier les ressources à partir d’un besoin fonctionnel
Méthode pas à pas
Objectif : partir d’un besoin fonctionnel (user story, cas d’usage) et en extraire des ressources stables, avec des noms cohérents.
Écrire le besoin en phrases simples (sans jargon technique). Exemple : “Un client passe une commande, consulte son historique, et suit la livraison.”
Surligner les noms (candidats entités) : client, commande, historique, livraison.
Continuez dans notre application.- Écoutez le fichier audio avec l'écran éteint.
- Obtenez un certificat à la fin du programme.
- Plus de 5000 cours à découvrir !
Téléchargez l'application
Surligner les verbes (candidats actions) : passer, consulter, suivre.
Transformer les entités en ressources :
customers,orders,shipments. “Historique” devient souvent une vue sur une collection existante (ex./customers/{id}/orders) plutôt qu’une ressource autonome.Reformuler les actions en opérations sur ressources : “passer une commande” devient
POST /orders(création), “consulter l’historique” devientGET /customers/{id}/orders, “suivre la livraison” devientGET /shipments/{id}ouGET /orders/{id}/shipment.Valider l’identifiabilité : une ressource doit pouvoir être référencée par un identifiant stable (ex.
orderId). Si ce n’est pas le cas, c’est peut-être une opération, un calcul, ou une sous-ressource.Définir les relations : une commande appartient à un client, une livraison est associée à une commande, etc.
Distinguer entités et actions (et éviter les endpoints “verbeux”)
Un piège courant est de créer des routes orientées actions, par exemple POST /orders/validate ou POST /orders/cancel. Avant de le faire, vérifiez si l’action correspond plutôt à :
- Un changement d’état d’une ressource : annuler une commande peut être
PATCH /orders/{id}avec{"status":"cancelled"}. - La création d’une ressource “événement” : si vous devez tracer qui annule et pourquoi, vous pouvez créer
POST /orders/{id}/cancellations(collection de demandes/événements). - Une ressource de “processus” : pour un traitement asynchrone,
POST /exportscrée un export, puisGET /exports/{id}permet de suivre son état.
Choisir des noms cohérents
- Noms au pluriel pour les collections :
/orders,/users. - Noms concrets et métier : préférez
invoicesàbillingDocumentssi c’est le terme métier. - Éviter les abréviations ambiguës :
/cfgou/usrnuisent à la lisibilité. - Stabilité : une fois publié, un nom de ressource est un contrat. Mieux vaut un nom légèrement générique mais durable qu’un nom trop spécifique.
Modélisation simple des données
Attributs : distinguer “donnée” et “dérivé”
Pour chaque ressource, listez :
- Attributs persistés : stockés en base (ex.
totalAmount,status,createdAt). - Attributs calculés : dérivés d’autres champs (ex.
isOverdue,itemsCount). Ils peuvent être exposés, mais ne doivent pas devenir la source de vérité.
Exemple de ressource Order (vue API) :
{"id":"ord_123","customerId":"cus_42","status":"paid","currency":"EUR","totalAmount":129.90,"itemsCount":3,"createdAt":"2026-01-10T14:12:03Z"}Relations : 1–1, 1–N, N–N
Modélisez les relations en pensant à la navigation côté API (comment un client va “suivre” les liens) et à la stabilité des identifiants.
| Type | Exemple métier | Représentation API courante |
|---|---|---|
| 1–1 | Utilisateur ↔ Profil | profileId dans User ou sous-ressource /users/{id}/profile |
| 1–N | Client → Commandes | customerId dans Order + collection /customers/{id}/orders |
| N–N | Produit ↔ Catégorie | table de jointure (interne) + endpoints /products/{id}/categories ou ressource d’association |
Quand créer une ressource d’association (N–N)
Pour une relation N–N, si l’association porte des attributs (ex. date d’ajout, rôle, quantité), il est souvent préférable d’exposer une ressource dédiée.
Exemple : un User appartient à des Teams avec un rôle :
- Ressources :
users,teams, etmemberships(association). - URI possibles :
/memberships/{id},/teams/{id}/memberships,/users/{id}/memberships.
{"id":"m_900","userId":"u_12","teamId":"t_3","role":"admin","createdAt":"2026-01-05T09:00:00Z"}Identifiants stables : règles pratiques
- Un identifiant ne doit pas dépendre d’un attribut modifiable (évitez email, nom, code métier susceptible de changer).
- Préférer un identifiant opaque (UUID, ULID, identifiant interne) pour limiter les fuites d’informations et les collisions.
- Ne pas réutiliser un identifiant supprimé (évite des effets de bord dans les caches, logs, intégrations).
- Conserver un champ “publicId” si nécessaire : si vous avez un ID interne numérique, vous pouvez exposer un identifiant public distinct.
Conventions de représentation JSON
Choisir une convention de nommage (camelCase ou snake_case)
Choisissez une convention et appliquez-la partout (champs, paramètres, réponses d’erreur). Le plus important est la cohérence.
- camelCase :
createdAt,customerId - snake_case :
created_at,customer_id
Exemple cohérent en camelCase :
{"id":"u_1","firstName":"Sam","lastName":"Lee","email":"sam@example.com","createdAt":"2026-01-10T14:12:03Z"}Dates et heures : ISO 8601
- Utilisez des chaînes ISO 8601, idéalement en UTC avec
Z:2026-01-10T14:12:03Z. - Évitez les formats locaux (
10/01/2026) et les timestamps ambigus sans fuseau. - Si vous devez gérer des dates “sans heure” (anniversaire, date de facturation), utilisez
YYYY-MM-DDet documentez-le.
Booléens, null et champs optionnels
- Booléens : utilisez
true/false, pas"yes"/"no". - null : signifie “valeur inconnue ou absente”. Ne l’utilisez pas pour signifier “faux”.
- Champs optionnels : deux approches cohérentes possibles : (1) omettre le champ s’il n’existe pas, (2) inclure le champ avec
null. Choisissez une règle et tenez-vous-y.
Exemple : téléphone optionnel (approche “champ omis”) :
{"id":"u_1","email":"sam@example.com"}Exemple : téléphone optionnel (approche “null explicite”) :
{"id":"u_1","email":"sam@example.com","phone":null}Champs calculés : comment les exposer sans ambiguïté
Un champ calculé doit être clairement dérivé et ne pas être accepté en écriture (ou être ignoré). Par exemple, itemsCount est calculé depuis la liste des lignes de commande.
{"id":"ord_123","items":[{"productId":"p_1","quantity":2},{"productId":"p_2","quantity":1}],"itemsCount":3}Champs sensibles : protéger et séparer modèle interne et ressource exposée
Identifier les données sensibles
Listez explicitement les champs qui ne doivent jamais sortir tels quels :
- Secrets : mots de passe (même hashés), tokens, clés API.
- Données personnelles : selon le contexte, adresse, téléphone, date de naissance, identifiants nationaux.
- Données de sécurité : rôles internes, flags anti-fraude, notes de modération.
- Données techniques : IDs internes de base, informations d’infrastructure, traces.
Principe : modèle interne ≠ DTO (ressource API)
Le modèle interne (ex. entité ORM) reflète les besoins de persistance et de logique métier. Le DTO (Data Transfer Object) ou “ressource exposée” reflète le contrat public. Ne pas les confondre évite :
- l’exposition accidentelle de champs sensibles,
- la rigidité (impossible de faire évoluer la base sans casser l’API),
- les incohérences (champs calculés persistés par erreur, etc.).
Exemple concret : entité interne vs ressource exposée
Entité interne UserEntity (exemple simplifié) :
{"id":123,"email":"sam@example.com","passwordHash":"$2b$10$...","isAdmin":true,"createdAt":"2026-01-10T14:12:03Z","lastLoginIp":"203.0.113.10"}DTO exposé UserResource :
{"id":"u_123","email":"sam@example.com","createdAt":"2026-01-10T14:12:03Z"}Points clés :
passwordHashn’est jamais exposé.isAdminpeut être remplacé par une information plus adaptée (ex.roles) ou omis selon le besoin.lastLoginIpest une donnée sensible/technique : à exclure ou à exposer uniquement dans un contexte d’administration strict.idexposé peut être un identifiant public différent du numérique interne.
Étapes pratiques pour concevoir vos DTO
Partir des écrans/clients : quelles informations sont nécessaires pour chaque cas d’usage (liste, détail, édition) ?
Créer un DTO par “vue” si besoin : par exemple
OrderSummarypour une liste etOrderDetailpour le détail, plutôt que de tout renvoyer partout.Définir explicitement les champs en lecture et en écriture : certains champs sont read-only (ex.
createdAt), d’autres write-only (ex. unpassworden création, jamais renvoyé).Mapper systématiquement : utilisez une couche de mapping (manuelle ou outillée) qui liste les champs autorisés. Évitez le “retour direct” d’entités persistées.
Ajouter des garde-fous : tests automatisés qui vérifient qu’aucun champ interdit n’apparaît dans les réponses.
Exemple : DTO d’écriture séparé (création d’utilisateur)
Requête (DTO d’entrée) :
{"email":"sam@example.com","password":"S3cret!","firstName":"Sam","lastName":"Lee"}Réponse (DTO de sortie) :
{"id":"u_123","email":"sam@example.com","firstName":"Sam","lastName":"Lee","createdAt":"2026-01-10T14:12:03Z"}Le champ password est write-only : accepté en entrée, jamais renvoyé.