Skip to Content
BackendConventions API REST

Conventions API REST

Standards pratiques pour le design, l’implémentation et le débogage d’APIs HTTP.

Nommage des URL

Les ressources se placent au pluriel, en kebab-case. Les sous-ressources utilisent des slashs.

PatternExempleSémantique
RessourceGET /usersLister tous les utilisateurs
Ressource uniqueGET /users/42Récupérer l’utilisateur 42
Sous-ressourceGET /users/42/postsPosts de l’utilisateur 42
CollectionGET /users/42/friendsListe des amis

BON : /users/42/posts/15

MAUVAIS : /Users/42/GetPosts/15

MAUVAIS : /user/42/post/15 (singulier, CamelCase)

MAUVAIS : /getUsers/42/posts/15 (verbe dans l’URL)

Règle d’or : l’URL décrit quoi, pas faire. Le verbe se trouve dans la méthode HTTP.

Méthodes HTTP

MéthodeSécuritéIdempotenceUsage
GETOui (ne modifie pas)OuiLecture seule
HEADOuiOuiHeaders uniquement (vérification)
POSTNonNonCréation de ressource
PUTNonOuiRemplacement complet
PATCHNonNonMise à jour partielle
DELETENonOuiSuppression
OPTIONSOuiOuiDécouvrir les méthodes autorisées

PUT vs PATCH — Quand utiliser lequel

// PUT : on remplace TOUT l'utilisateur fetch('/api/users/42', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Arthur', email: 'arthur@example.com', role: 'admin', avatar: 'https://...', // Tous les champs doivent être fournis }), }); // PATCH : on ne modifie que certains champs fetch('/api/users/42', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ avatar: 'https://...' }), });
CritèrePUTPATCH
Champs manquantsConsidérés comme null (remplacés)Ignorés (conservés)
IdempotenceOui — plusieurs PUT = même résultatNon — plusieurs PATCH peuvent différer
Cas d’usageConfiguration, préférences complètesModifications ciblées

Codes de statut

Succès — 2xx

CodeSignificationQuand
200 OKRequête réussieGET, PUT, PATCH, DELETE
201 CreatedRessource crééePOST réussi
204 No ContentSuccès, sans corpsDELETE réussi

Redirections — 3xx

CodeSignificationQuand
301 Moved PermanentlyURL définitivement changéeRenommage permanent de ressource
302 FoundRedirection temporaireMigration en cours
304 Not ModifiedCache valideIf-None-Match / ETag

Erreurs client — 4xx

CodeSignificationQuand
400 Bad RequestRequête invalideJSON malformé, paramètre manquant
401 UnauthorizedAuthentification requiseToken absent ou invalide
403 ForbiddenNon autoriséRôle insuffisant
404 Not FoundRessource introuvableID inexistant
405 Method Not AllowedMéthode interditeMauvaise méthode HTTP
409 ConflictConflitDuplicate (ex. email existant)
422 Unprocessable EntitySémantique invalideValidation métier échouée
429 Too Many RequestsRate limit dépasséTrop de requêtes

Erreurs serveur — 5xx

CodeSignificationQuand
500 Internal Server ErrorErreur génériqueBugs, exceptions non gérées
502 Bad GatewayPasserelle invalideService aval hors-ligne
503 Service UnavailableIndisponibilitéMaintenance, surcharge

Piège : 400 vs 422. Un 400 concerne la syntaxe (JSON invalide, type incorrect). Un 422 concerne la sémantique (champs valides syntaxiquement mais interdits par les règles métier).

Headers essentiels

En-têteClient → ServeurServeur → ClientUsage
Content-TypeOuiType du payload (application/json)
AcceptOuiType attendu de la réponse
AuthorizationOuiToken (Bearer <token>)
X-Request-IDOuiCorrélation des logs
X-RateLimit-LimitOuiLimite de requêtes par fenêtre
X-RateLimit-RemainingOuiRequêtes restantes
X-RateLimit-ResetOuiTimestamp de reset (Unix)
ETagOuiVersion de la ressource (cache)
If-None-MatchOuiVérifier la mise à jour
Cache-ControlOuiStratégie de cache (max-age, no-store)
LinkOuiPagination (relation next, prev)
Retry-AfterOuiDélai avant nouvelle tentative

Exemple de headers en production :

HTTP/1.1 200 OK Content-Type: application/json ETag: "a1b2c3d4" X-Request-ID: req-7f8a9b0c X-RateLimit-Limit: 100 X-RateLimit-Remaining: 97 X-RateLimit-Reset: 1720000000 Cache-Control: public, max-age=60 Link: <https://api.exemple.com/users?page=2>; rel="next"

Pagination

Offset (numérique)

Simple, adapté aux petites API. Inefficace pour les gros volumes (décalage des résultats lors de suppressions).

GET /api/users?offset=0&limit=20 GET /api/users?offset=20&limit=20
{ "data": [ ... ], "pagination": { "offset": 0, "limit": 20, "total": 150, "hasMore": true } }

Cursor (position)

Recommandé pour les APIs avec volumes importants. Résiste aux modifications concurrentes.

GET /api/users?cursor=eyJpZCI6NDJ9&limit=20
{ "data": [ ... ], "pagination": { "limit": 20, "nextCursor": "eyJpZCI6NjJ9", "prevCursor": "eyJpZCI6MjJ9" } }

Piège : avec la pagination offset, les suppressions entre deux pages peuvent faire « sauter » des éléments ou les dupliquer. Pour les feeds et timelines, toujours utiliser cursor.

Pour les APIs nécessitant une navigation puissante, renvoyer les liens de pagination dans un en-tête Link :

Link: <https://api.exemple.com/users?page=1>; rel="first", <https://api.exemple.com/users?page=2>; rel="next", <https://api.exemple.com/users?page=8>; rel="last"

Format des erreurs

Une réponse d’erreur standardisée facilite le traitement côté client.

{ "error": { "code": "VALIDATION_ERROR", "status": 422, "message": "La validation a échoué.", "details": [ { "field": "email", "message": "Cet email est déjà utilisé.", "rule": "unique" }, { "field": "password", "message": "Minimum 8 caractères requis.", "rule": "min_length" } ], "requestId": "req-7f8a9b0c" } }

Champs recommandés :

ChampRequisDescription
error.codeOuiClé machine lisible (ex. VALIDATION_ERROR, NOT_FOUND)
error.statusOuiCode HTTP correspondant
error.messageOuiMessage lisible par un humain
error.details[]NonErreurs fines par champ (validations)
error.requestIdOuiID de corrélation pour le support
// Traitement côté client async function apiCall(url, opts = {}) { const res = await fetch(url, opts); if (!res.ok) { const body = await res.json(); const err = body.error || { code: 'UNKNOWN', status: res.status, message: res.statusText, }; // Exemple de mapping par code switch (err.code) { case 'UNAUTHORIZED': // Rediriger vers la page de connexion window.location.href = '/login'; break; case 'RATE_LIMITED': // Attendre le retry const retryAfter = res.headers.get('Retry-After'); setTimeout(() => apiCall(url, opts), retryAfter * 1000); return; } throw new APIError(err); } return res.json(); }

Filtrage et tri

Requête simple

GET /api/posts?status=published&author=42&created_after=2025-01-01
  • Paramètres avec valeur unique : ?status=published
  • Paramètres multiples : ?role=admin&role=editor (tableau) ou ?role[]=admin&role[]=editor
  • Filtres sur relations : ?author.name=Arthur (dot notation)

Tri

GET /api/posts?sort=-published_at,+title
  • - = ordre décroissant
  • + ou rien = ordre croissant
  • Plusieurs champs : virgule

Recherche texte

GET /api/posts?q=recherche+en+français&page=1

Piège : toujours sanitizer les paramètres côté serveur. ?q=&lt;script&gt;alert(1)&lt;/script&gt; ne doit jamais passer en clair au SGBD sans échappement.

Versioning

Choisir une stratégie tôt, avant le premier release public.

StratégieExempleAvantagesInconvénients
HeaderAccept: application/vnd.api.v2+jsonURLs propres, backward compatibleMoins visible, besoin de doc
URL path/api/v2/usersVisible, facile à comprendreURLs qui vieillissent mal
Query param/api/users?version=2SimpleMoins standard

Recommandé : header Accept pour les APIs publiques, path /v2/ pour les APIs internes.


Checklist de design API

  • URL au pluriel, kebab-case, pas de verbes
  • Méthode HTTP correspond à la sémantique (PUT vs PATCH)
  • Code de statut pertinent (4xx pour client, 5xx pour serveur)
  • Format d’erreur standardisé avec requestId
  • Pagination offset ou cursor (pas les deux)
  • Headers X-RateLimit-* pour informer le client
  • ETag pour le cache côté client
  • Stratégie de versioning choisie et documentée