API REST back-end : définir ressources et modèles de données

Capítulo 1

Temps de lecture estimé : 8 minutes

+ Exercice

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.

  1. Écrire le besoin en phrases simples (sans jargon technique). Exemple : “Un client passe une commande, consulte son historique, et suit la livraison.”

  2. 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 !
    Ou poursuivez votre lecture ci-dessous...
    Download App

    Téléchargez l'application

  3. Surligner les verbes (candidats actions) : passer, consulter, suivre.

  4. 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.

  5. Reformuler les actions en opérations sur ressources : “passer une commande” devient POST /orders (création), “consulter l’historique” devient GET /customers/{id}/orders, “suivre la livraison” devient GET /shipments/{id} ou GET /orders/{id}/shipment.

  6. 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.

  7. 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 /exports crée un export, puis GET /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 à billingDocuments si c’est le terme métier.
  • Éviter les abréviations ambiguës : /cfg ou /usr nuisent à 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.

TypeExemple métierReprésentation API courante
1–1Utilisateur ↔ ProfilprofileId dans User ou sous-ressource /users/{id}/profile
1–NClient → CommandescustomerId dans Order + collection /customers/{id}/orders
N–NProduit ↔ Catégorietable 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, et memberships (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-DD et 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 :

  • passwordHash n’est jamais exposé.
  • isAdmin peut être remplacé par une information plus adaptée (ex. roles) ou omis selon le besoin.
  • lastLoginIp est une donnée sensible/technique : à exclure ou à exposer uniquement dans un contexte d’administration strict.
  • id exposé peut être un identifiant public différent du numérique interne.

Étapes pratiques pour concevoir vos DTO

  1. Partir des écrans/clients : quelles informations sont nécessaires pour chaque cas d’usage (liste, détail, édition) ?

  2. Créer un DTO par “vue” si besoin : par exemple OrderSummary pour une liste et OrderDetail pour le détail, plutôt que de tout renvoyer partout.

  3. Définir explicitement les champs en lecture et en écriture : certains champs sont read-only (ex. createdAt), d’autres write-only (ex. un password en création, jamais renvoyé).

  4. 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.

  5. 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é.

Répondez maintenant à l’exercice sur le contenu :

Pour éviter des endpoints orientés actions comme POST /orders/cancel, quelle approche REST est la plus adaptée quand « annuler une commande » correspond à un changement d’état de la ressource ?

Tu as raison! Félicitations, passez maintenant à la page suivante

Vous avez raté! Essayer à nouveau.

Si l’« annulation » est un changement d’état d’une commande, l’approche REST consiste à modifier la ressource Order via PATCH sur /orders/{id} en mettant à jour un attribut comme status, plutôt que de créer un endpoint verbeux centré sur une action.

Chapitre suivant

API REST back-end : concevoir des endpoints lisibles et cohérents

Arrow Right Icon
Couverture de livre électronique gratuite Développement back-end : concevoir une API REST propre
13%

Développement back-end : concevoir une API REST propre

Nouveau cours

8 pages

Téléchargez l'application pour obtenir une certification gratuite et écouter des cours en arrière-plan, même avec l'écran éteint.