REST API design - 09 - Erreurs : un format que tes clients vont adorer

Comment structurer tes réponses d'erreur avec RFC 7807 Problem Détails, des codes métier et une vraie stratégie d'internationalisation.

REST API design 10 / 25
  1. 01 REST API design - 00 - Pourquoi le design de ton API change tout
  2. 02 REST API design - 01 - Les principes REST que personne ne lit
  3. 03 REST API design - 02 - Des URLs qui ont du sens
  4. 04 REST API design - 03 - Les méthodes HTTP, pour de vrai
  5. 05 REST API design - 04 - Les codes de statut HTTP qu'il faut connaître
  6. 06 REST API design - 05 - Body, headers et le diable dans les détails
  7. 07 REST API design - 06 - La pagination, ou comment ne pas tuer ta base
  8. 08 REST API design - 07 - Filtrage et tri sans prise de tête
  9. 09 REST API design - 08 - La validation avec Zod, gardien de ton API
  10. 10 REST API design - 09 - Erreurs : un format que tes clients vont adorer
  11. 11 REST API design - 10 - Authentification : JWT, API keys et OAuth2
  12. 12 REST API design - 11 - Versioning : quand et comment faire évoluer ton API
  13. 13 REST API design - 12 - CORS : comprendre et debugger les erreurs cross-origin
  14. 14 REST API design - 13 - Rate limiting : protéger ton API sans frustrer tes clients
  15. 15 REST API design - 14 - Caching : les bonnes réponses sont celles qu'on n'envoie pas
  16. 16 REST API design - 15 - Upload de fichiers : multipart, signed URLs et streaming
  17. 17 REST API design - 16 - Relations entre ressources : embarquer, lier ou les deux
  18. 18 REST API design - 17 - HATEOAS : des liens dans tes réponses
  19. 19 REST API design - 18 - OpenAPI : le contrat entre ton API et le monde
  20. 20 REST API design - 19 - Documentation : une API non documentee est une API inutile
  21. 21 REST API design - 20 - Testing : tester ton API sans devenir fou
  22. 22 REST API design - 21 - Webhooks : quand c'est ton API qui appelle
  23. 23 REST API design - 22 - Performance : quand chaque milliseconde compte
  24. 24 REST API design - 23 - Sécurité : les attaques que tu vas subir (et comment t'en protéger)
  25. 25 REST API design - 24 - Glossaire : tous les termes REST API en un seul endroit

09 - Erreurs : un format que tes clients vont adorer

Ce que tu vas apprendre

  • Le format RFC 7807 Problem Détails et ses cinq champs
  • La différence entre codes HTTP et codes d'erreur métier
  • Comment gerer l'internationalisation des messages d'erreur
  • Pourquoi documenter ses erreurs change la vie de tout le monde

Prerequisites

Avoir lu les articles précédents sur les verbes HTTP et les status codes. Savoir ce qu'est un middleware dans un framework web (Express, Elysia, Fastify).


J'ai travaille sur une API ou chaque endpoint renvoyait ses erreurs dans un format différent. Un endpoint retournait { error: "not found" }, un autre { message: "Not Found", code: 404 }, un troisieme { errors: ["Not found"] }. Le front devait gerer trois parsers différents pour la meme information. C'etait un cauchemar.

Le problème des erreurs non standardisees

Quand tu n'as pas de format d'erreur uniforme, chaque développeur invente le sien. Le front finit par écrire du code defensif partout :

typescript// Le front qui essaie de comprendre l'erreur
const message = error.message || error.error || error.errors?.[0] || "Erreur inconnue";

C'est fragile, c'est penible, et ca casse des qu'un nouveau dev ajoute un endpoint avec un quatrieme format.

RFC 7807 Problem Détails

La RFC 7807 definit un format standard pour les erreurs HTTP. Cinq champs, pas plus :

json{
  "type": "https://api.paltemps.fr/errors/validation-failed",
  "title": "Validation Failed",
  "status": 422,
  "detail": "Le champ 'email' ne contient pas une adresse valide.",
  "instance": "/users/42"
}
  • type : une URI qui identifié le type d'erreur. Idealement un lien vers la doc.
  • title : un résumé lisible par un humain. Toujours le meme pour un meme type.
  • status : le code HTTP. Redondant avec le header, mais pratique quand tu logues le body.
  • détail : le message spécifique a cette occurrence. C'est ici que tu mets les détails.
  • instance : l'URI de la ressource concernee (optionnel mais utile pour le debug).

Le Content-Type de la réponse doit etre application/problem+json.

Codes HTTP vs codes d'erreur métier

Les codes HTTP couvrent les cas génériques : 400 (requête invalide), 404 (pas trouve), 422 (validation), 500 (erreur serveur). Mais ils ne suffisent pas pour distinguer tes erreurs métier.

Exemple : un 422 peut signifier "email invalide" ou "quota dépassé". Le code HTTP est le meme. C'est le champ type qui fait la différence :

typescript// Erreur de validation
{
  type: "https://api.example.com/errors/invalid-email",
  title: "Invalid Email",
  status: 422,
  detail: "L'adresse 'toto@' n'est pas un email valide."
}

// Erreur de quota
{
  type: "https://api.example.com/errors/quota-exceeded",
  title: "Quota Exceeded",
  status: 422,
  detail: "Tu as atteint la limite de 100 projets."
}

Le front peut brancher sa logique sur le type plutot que sur le status. Beaucoup plus precis.

L'enveloppe d'erreur etendue

La RFC 7807 permet d'ajouter des champs supplementaires. Pour les erreurs de validation, j'ajoute souvent un tableau errors :

json{
  "type": "https://api.example.com/errors/validation-failed",
  "title": "Validation Failed",
  "status": 422,
  "detail": "2 champs sont invalides.",
  "errors": [
    { "field": "email", "message": "Format invalide", "code": "INVALID_FORMAT" },
    { "field": "age", "message": "Doit etre positif", "code": "MIN_VALUE" }
  ]
}

Le champ code dans chaque erreur est un identifiant stable que le front peut utiliser pour afficher des messages traduits. "INVALID_FORMAT" ne changera jamais, meme si tu reformules le message cote serveur.

Internationalisation des messages

Deux approches. La première : le serveur renvoie le message dans la langue demandee via le header Accept-Language :

typescriptapp.use((req, res, next) => {
  const lang = req.headers["accept-language"]?.split(",")[0] || "en";
  req.locale = lang.startsWith("fr") ? "fr" : "en";
  next();
});

La deuxieme (que je préféré) : le serveur renvoie un code machine (INVALID_FORMAT) et c'est le front qui traduit. Le serveur n'a pas a connaître les traductions. Le front a deja un système i18n (i18next, vue-i18n, etc.), autant l'utiliser.

typescript// Cote front
const errorMessages: Record<string, Record<string, string>> = {
  fr: { INVALID_FORMAT: "Le format est invalide", MIN_VALUE: "La valeur minimum n'est pas respectee" },
  en: { INVALID_FORMAT: "Invalid format", MIN_VALUE: "Minimum value not met" }
};

Un middleware d'erreur centralise

Ne gere pas les erreurs dans chaque route. Un seul middleware qui attrape tout :

typescriptclass AppError extends Error {
  constructor(
    public type: string,
    public title: string,
    public status: number,
    public detail: string
  ) {
    super(detail);
  }
}

// Middleware Express
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  if (err instanceof AppError) {
    return res.status(err.status).json({
      type: `https://api.example.com/errors/${err.type}`,
      title: err.title,
      status: err.status,
      detail: err.detail,
      instance: req.originalUrl
    });
  }

  // Erreur non prevue
  console.error(err);
  res.status(500).json({
    type: "https://api.example.com/errors/internal",
    title: "Internal Server Error",
    status: 500,
    detail: "Une erreur inattendue s'est produite."
  });
});

L'avantage : chaque route lance juste throw new AppError(...) et le middleware fait le reste.

Documenter ses erreurs

Chaque type URI devrait pointer vers une page de documentation qui explique :

  • Ce que l'erreur signifie
  • Ce que le client peut faire pour la corriger
  • Un exemple de requête qui provoque cette erreur

C'est un investissement qui se rentabilise vite. Moins de tickets support, moins de questions sur Slack, moins de frustration.

Résumé

  • Utilise RFC 7807 Problem Détails pour toutes tes erreurs : type, title, status, detail, instance
  • Ajoute des codes d'erreur métier dans le champ type pour distinguer les erreurs qui partagent le meme status HTTP
  • Pour l'i18n, renvoie des codes machine et laisse le front traduire
  • Centralise la gestion d'erreur dans un middleware unique
  • Documente chaque type d'erreur avec des exemples

Article précédent : Validation des donnees Article suivant : Authentification

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.