API REST back-end : gestion d’erreurs propre et traçabilité

Capítulo 6

Temps de lecture estimé : 8 minutes

+ Exercice

Objectifs : erreurs prévisibles, réponses cohérentes, traçabilité exploitable

Une API « propre » ne se contente pas de renvoyer un statut HTTP : elle fournit un format d’erreur uniforme, permet de relier une réponse à des logs via un identifiant de corrélation (traceId), et évite toute fuite d’informations sensibles. L’objectif est double : aider le client à corriger sa requête (erreurs côté client) et aider l’équipe back-end à diagnostiquer rapidement (erreurs côté serveur) sans exposer de détails internes.

Mise en place d’un format d’erreur uniforme

Structure recommandée

Adoptez un objet JSON stable, identique pour toutes les erreurs, quel que soit le statut HTTP. Exemple de structure :

{  "errorCode": "USER_EMAIL_INVALID",  "message": "L’adresse e-mail est invalide.",  "details": [    { "field": "email", "issue": "format" }  ],  "traceId": "01HZY2..."}
  • errorCode : code stable, lisible, non localisé. Sert au support, au monitoring, et à la logique côté client.
  • message : message destiné à l’utilisateur final ou au développeur client (selon votre produit). Peut être localisé.
  • details : informations structurées pour guider la correction (liste d’erreurs de champs, contraintes, paramètres). Optionnel.
  • traceId : identifiant de corrélation, présent dans la réponse et dans les logs. Toujours présent si possible.

Champs optionnels utiles (sans surcharger)

Selon vos besoins, vous pouvez ajouter des champs optionnels, en restant cohérent :

  • timestamp : date ISO 8601 côté serveur.
  • path : chemin de la requête (ex. /users/123).
  • method : verbe HTTP.
  • hint : suggestion courte (ex. « Vérifiez le format RFC 5322 ») sans révéler d’implémentation interne.
  • retryable : booléen indiquant si un retry a du sens (utile pour 503/429).

Localisation (i18n) des messages

Deux approches courantes :

  • Localiser message côté serveur en fonction de Accept-Language. Avantage : expérience utilisateur immédiate. Inconvénient : nécessite un catalogue de traductions côté API.
  • Laisser message générique et localiser côté client via errorCode. Avantage : API plus simple. Inconvénient : le client doit maintenir les traductions.

Bonne pratique : conserver errorCode comme source de vérité et ne jamais le traduire. Si vous localisez message, assurez-vous qu’il reste stable et non ambigu.

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

Étapes pratiques : standardiser les erreurs

  1. Définir un contrat : documentez la structure JSON et la liste des errorCode (au moins par domaine fonctionnel).
  2. Créer un “ErrorResponse builder” : une fonction unique qui construit l’objet (ajout de traceId, timestamp, etc.).
  3. Centraliser le traitement des exceptions : un middleware/filtre global qui intercepte les erreurs et renvoie le format uniforme.
  4. Imposer des tests : tests d’intégration vérifiant que chaque catégorie d’erreur renvoie bien errorCode et traceId.

Catégorisation des erreurs : une taxonomie opérationnelle

Catégoriser permet d’avoir des réponses cohérentes, des logs exploitables et des alertes pertinentes. Voici une classification pratique, alignée sur les causes réelles.

1) Erreurs côté client (entrées invalides)

Cas typiques : paramètres manquants, formats incorrects, incohérences simples. Réponse : errorCode explicite + details structurés.

{  "errorCode": "REQUEST_PARAMETER_INVALID",  "message": "Un ou plusieurs paramètres sont invalides.",  "details": [    { "field": "limit", "issue": "must_be_between", "min": 1, "max": 100 }  ],  "traceId": "01HZY2..."}

2) Authentification / autorisation

Distinguez clairement :

  • Non authentifié : absence/invalidité des identifiants (token expiré, signature invalide).
  • Non autorisé : authentifié mais droits insuffisants.

Évitez de divulguer des détails (ex. « utilisateur inexistant ») qui facilitent l’énumération. Préférez des messages neutres.

{  "errorCode": "AUTH_REQUIRED",  "message": "Authentification requise.",  "traceId": "01HZY2..."}
{  "errorCode": "FORBIDDEN",  "message": "Accès non autorisé.",  "traceId": "01HZY2..."}

3) Conflits

Conflits d’état : ressource déjà existante, concurrence, version obsolète, règle d’unicité. Le details peut indiquer la nature du conflit sans exposer de données sensibles.

{  "errorCode": "RESOURCE_CONFLICT",  "message": "Conflit : la ressource ne peut pas être modifiée dans son état actuel.",  "details": [    { "reason": "version_mismatch" }  ],  "traceId": "01HZY2..."}

4) Ressources absentes

Quand une ressource n’existe pas (ou n’est pas visible), gardez une réponse cohérente. Selon votre modèle de sécurité, vous pouvez choisir de ne pas distinguer « absent » et « non autorisé » pour éviter la fuite d’information. Dans tous les cas, le format reste identique.

{  "errorCode": "RESOURCE_NOT_FOUND",  "message": "Ressource introuvable.",  "traceId": "01HZY2..."}

5) Erreurs serveur

Exceptions inattendues, bugs, erreurs d’infrastructure. Ne renvoyez jamais la stack trace au client. Utilisez traceId pour permettre le diagnostic côté serveur.

{  "errorCode": "INTERNAL_ERROR",  "message": "Une erreur interne est survenue.",  "traceId": "01HZY2..."}

Journalisation (logs) : diagnostiquer sans fuite de secrets

Principes de base

  • Logs structurés (JSON) : facilitent la recherche et les agrégations (par traceId, errorCode, status).
  • Pas de secrets : ne loggez jamais tokens, mots de passe, clés API, cookies de session, numéros complets de cartes, etc.
  • Masquage systématique : appliquez une stratégie de redaction (ex. remplacer par *** ou hachage partiel).
  • Éviter les dumps : ne loggez pas le corps complet des requêtes/réponses en production sans filtrage.

Correlation ID / traceId : génération et propagation

Le traceId doit suivre la requête de bout en bout (API → services internes → dépendances). Deux options :

  • Accepter un identifiant entrant (ex. en-tête X-Request-Id) et le réutiliser s’il est valide.
  • Générer un identifiant si absent, puis le renvoyer dans la réponse et l’injecter dans tous les logs.

Étapes pratiques :

  1. Middleware d’entrée : lire X-Request-Id (ou équivalent), sinon générer un UUID/ULID.
  2. Contexte de requête : stocker le traceId dans un contexte accessible (thread-local, context async, etc.).
  3. Propagation sortante : ajouter traceId aux appels HTTP sortants (en-tête) et aux messages asynchrones.
  4. Réponse : inclure traceId dans le JSON d’erreur et idéalement dans un en-tête (ex. X-Request-Id).

Exemple de log structuré (serveur)

{  "level": "ERROR",  "event": "api_request_failed",  "traceId": "01HZY2...",  "errorCode": "INTERNAL_ERROR",  "http": {    "method": "POST",    "path": "/orders",    "status": 500  },  "user": { "id": "u_123" },  "durationMs": 84}

Remarque : la stack trace peut être loggée côté serveur (niveau ERROR) mais jamais renvoyée au client. Si vous la loggez, assurez-vous qu’elle ne contient pas de secrets (certaines bibliothèques incluent des valeurs de paramètres).

Mapping interne des exceptions vers des réponses HTTP cohérentes

Une API maintenable évite les try/catch dispersés. Le mapping doit être centralisé : chaque exception métier/technique est convertie en un couple (statut HTTP, errorCode, message, details).

Étapes pratiques : construire un mapping

  1. Définir une hiérarchie d’erreurs internes : par exemple DomainError, AuthError, ConflictError, NotFoundError, DependencyError.
  2. Associer un errorCode à chaque type : stable, versionné si nécessaire.
  3. Créer un handler global : transforme toute exception en ErrorResponse uniforme.
  4. Traiter les exceptions inconnues : fallback vers INTERNAL_ERROR + log complet côté serveur.

Table de mapping (exemple)

Catégorie interneExemple d’exceptionStatut HTTPerrorCodeNotes
Entrées invalidesInvalidParameterError400REQUEST_PARAMETER_INVALIDInclure details par champ
AuthentificationUnauthenticatedError401AUTH_REQUIREDMessage neutre
AutorisationForbiddenError403FORBIDDENNe pas révéler la règle exacte
AbsentNotFoundError404RESOURCE_NOT_FOUNDOption : masquer si non autorisé
ConflitConflictError409RESOURCE_CONFLICTPeut inclure reason
Dépendance indisponibleUpstreamTimeoutError503SERVICE_UNAVAILABLEMarquer retryable=true
InattenduException500INTERNAL_ERRORLog complet + traceId

Exemple de pseudo-code de handler global

function handleError(err, req):  traceId = req.context.traceId  if err is DomainError:    return json(err.httpStatus, {      errorCode: err.code,      message: localize(err.messageKey, req.locale),      details: err.details,      traceId: traceId    })  if err is UpstreamTimeoutError:    return json(503, {      errorCode: "SERVICE_UNAVAILABLE",      message: "Service temporairement indisponible.",      details: [{ "dependency": err.dependency }],      traceId: traceId,      retryable: true    })  log.error({ traceId, err })  return json(500, {    errorCode: "INTERNAL_ERROR",    message: "Une erreur interne est survenue.",    traceId: traceId  })

Stratégies de résilience : timeouts, dépendances, 503 cohérents

Timeouts : éviter les requêtes qui pendent

Sans timeouts, une dépendance lente peut saturer vos threads/connexions et provoquer un effet domino. Appliquez des timeouts à chaque appel sortant (HTTP, base de données, cache) et différenciez :

  • Timeout de connexion : temps max pour établir la connexion.
  • Timeout de lecture : temps max d’attente de réponse.
  • Timeout global : budget total par requête (deadline).

Étapes pratiques :

  1. Définir un budget par endpoint (ex. 800 ms) et le propager aux dépendances.
  2. Configurer les clients (HTTP/DB) avec des timeouts explicites.
  3. Gérer l’erreur : transformer un timeout en réponse cohérente (souvent 503) avec retryable.

Gestion des dépendances : dégradation contrôlée

Quand une dépendance est instable, vous pouvez :

  • Limiter la concurrence (bulkhead) : éviter qu’une dépendance consomme toutes les ressources.
  • Circuit breaker : couper temporairement les appels après un taux d’échec élevé.
  • Fallback : réponse partielle, cache, ou fonctionnalité dégradée (si acceptable).

Dans tous les cas, la réponse d’erreur doit rester uniforme et traçable.

Réponses 503 et JSON d’erreur cohérent

Pour une indisponibilité temporaire (dépendance down, surcharge, maintenance), renvoyez une erreur cohérente, sans détails internes (pas de nom de cluster, d’IP, etc.). Exemple :

{  "errorCode": "SERVICE_UNAVAILABLE",  "message": "Service temporairement indisponible. Veuillez réessayer plus tard.",  "details": [    { "type": "dependency", "name": "payment" }  ],  "traceId": "01HZY2...",  "retryable": true}

Bonnes pratiques :

  • Inclure traceId pour relier l’incident aux logs.
  • Rester vague dans message (pas d’informations d’infrastructure).
  • Optionnel : ajouter un en-tête Retry-After si vous connaissez un délai raisonnable.

Checklist de mise en œuvre

  • Un seul format JSON d’erreur (mêmes champs, mêmes types) pour toutes les routes.
  • errorCode stable et non localisé ; message éventuellement localisé.
  • traceId généré/propagé et présent dans la réponse + logs.
  • Handler global : mapping centralisé exceptions → réponses.
  • Logs structurés + redaction des secrets + pas de body brut en production.
  • Timeouts configurés partout + transformation des erreurs de dépendances en 503 cohérents.

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

Quel ensemble de pratiques correspond à une gestion d’erreurs « propre » et traçable dans une API REST ?

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

Vous avez raté! Essayer à nouveau.

Une approche propre combine un format JSON d’erreur stable (avec errorCode) et un traceId pour relier réponse et logs, un handler global pour garantir la cohérence, et la non-divulgation d’informations sensibles (pas de stack trace ni de secrets).

Chapitre suivant

API REST back-end : versioning et compatibilité dans le temps

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

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.