Skip to content

Gestion des erreurs fr FR

rocambille edited this page Jul 27, 2026 · 4 revisions

Résumé : Les erreurs sont inévitables. Cette page explique comment StartER les gère, et comment ajouter une gestion d'erreurs correcte à vos propres modules.

Ce que vous apprendrez :

  • Identifier quelles erreurs sont gérées par les middleware vs. votre code
  • Créer des erreurs avec des codes HTTP personnalisés
  • Comprendre comment les error boundaries React fonctionnent dans StartER

Le flux des erreurs dans StartER

Quand quelque chose échoue pendant une requête, StartER suit une chaîne claire :

  Requête
    │
    ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Chaîne de middlewares                        │
│                                                                 │
│  cookieParser → csrf → json → validateur → paramConverter       │
│       │           │             │              │                │
│       │           │        400 (body invalide) 404 (introuvable)|
│       │      401 (jeton                                         │
│       │       CSRF absent)                                      │
│       ▼                                                         │
│    Action                                                       │
│       │                                                         │
│       ├── 200/201/204 (succès)                                  │
│       └── throw Error ──────────────────────────┐               │
│                                                 │               │
└─────────────────────────────────────────────────│───────────────┘
                                                  │
                                                  ▼
                                         ┌────────────────┐
                                         │ logErrors      │
                                         │ (console.error)│
                                         └───────┬────────┘
                                                 │
                                                 ▼
                                         ┌────────────────┐
                                         │ sendErrors     │
                                         │ (réponse JSON) │
                                         └────────────────┘

L'idée clé : la plupart des erreurs sont déjà gérées avant que votre action ne s'exécute. La chaîne de middlewares intercepte :

  • 400 : corps de requête invalide (validateur)
  • 401 : jeton CSRF absent (middleware csrf), token d'authentification invalide/absent (verifyAccessToken)
  • 404 : ressource introuvable (param converter)
  • 403 : problème d'autorisation (checkAccess dans les routes)

Votre action n'a besoin de gérer que les erreurs spécifiques à sa logique métier.

Tableau de référence des codes de statut HTTP

StartER utilise res.sendStatus() pour la gestion légère des statuts (qui envoie les messages texte bruts standard d'Express) et Zod pour les erreurs de validation structurées 400 Bad Request :

Code de statut Nom du statut Méthode Express Déclencheur / Middleware Format du corps de réponse
400 Bad Request res.status(400).json(...) createValidator (échec de validation Zod) { issues: ZodIssue[] } (objet JSON)
401 Unauthorized res.sendStatus(401) Middleware verifyAccessToken ou csrf "Unauthorized" (texte brut)
404 Not Found res.sendStatus(404) (ou 204 sur DELETE) createParamConverter ou route API inconnue "Not Found" (texte brut) (ou corps vide pour 204)
409 (Optionnel) Conflict res.sendStatus(409) Action personnalisée (ex: gestion de doublon d'email) "Conflict" (texte brut)
422 (Optionnel) Unprocessable Entity res.status(422).json(...) Règle métier optionnelle dans votre module JSON ou texte brut personnalisé
500 Internal Server Error Middleware sendErrors Gestionnaire d'erreurs global dans server.ts Dev: { message, stack }
Prod: { message: "Internal Server Error" }

Les gestionnaires d'erreurs globaux

En bas de server.ts, deux middlewares de gestion d'erreurs interceptent tout ce qui passe à travers :

// 1. Logger l'erreur pour le débogage
const logErrors: ErrorRequestHandler = (err, req, _res, next) => {
  console.error(err);
  console.error("on req:", req.method, req.path);
  next(err);
};

// 2. Envoyer une réponse JSON structurée
const sendErrors: ErrorRequestHandler = (err, _req, res, _next) => {
  const status = err.status ?? err.statusCode ?? 500;
  res.status(status).json({
    message: err.message ?? "Internal Server Error",
    // La stack trace n'est exposée qu'en développement
    ...(isProduction ? {} : { stack: err.stack }),
  });
};

Cela signifie que toute erreur non interceptée dans votre action produira automatiquement une réponse 500 Internal Server Error en JSON. En développement, la stack trace est incluse pour faciliter le débogage. En production, elle est masquée.

Quand intercepter les erreurs dans vos actions

La plupart du temps, vous n'avez pas besoin de le faire. Laissez les erreurs remonter jusqu'au gestionnaire global. Mais il y a des cas où vous devez intercepter :

Cas 1 : vous voulez un code de statut spécifique

Si une opération de base de données échoue à cause d'une contrainte métier (par ex. violation d'unicité), vous pouvez vouloir renvoyer un 409 Conflict au lieu d'un 500 générique :

const add: RequestHandler = (req, res) => {
  try {
    const insertId = groupRepository.create(req.body);
    res.status(201).json({ insertId });
  } catch (err) {
    // SQLite lève une erreur sur les violations de contrainte UNIQUE
    if (err instanceof Error && err.message.includes("UNIQUE constraint")) {
      res.status(409).json({ message: "Un groupe avec ce nom existe déjà" });
      return;
    }

    // Relancer les erreurs inattendues vers le gestionnaire global
    throw err;
  }
};

Important

Relancez toujours les erreurs que vous ne gérez pas explicitement. Avaler les erreurs silencieusement est la source la plus courante de bugs « impossibles ».

Cas 2 : vous devez nettoyer un effet de bord

Si votre action effectue plusieurs opérations (par ex. créer un enregistrement et envoyer un email), vous pouvez avoir besoin d'intercepter l'échec de la seconde opération pour éviter un état partiel :

const register: RequestHandler = async (req, res) => {
  const userId = userRepository.create(req.body);

  try {
    await sendWelcomeEmail(req.body.email);
  } catch {
    // L'email a échoué, mais l'utilisateur a été créé.
    // Logger l'échec mais ne pas casser le flux.
    console.error(`Échec de l'envoi de l'email de bienvenue à ${req.body.email}`);
  }

  res.status(201).json({ insertId: userId });
};

Cas 3 : opérations transactionnelles

Pour les opérations multi-tables, utilisez les transactions SQL pour garantir l'atomicité. Voir Many-to-many et transactions pour le pattern complet avec BEGIN, COMMIT, et ROLLBACK.

Créer des erreurs avec des codes de statut

Express 5 supporte la convention err.status. Vous pouvez créer des erreurs avec des codes de statut spécifiques :

const edit: RequestHandler = (req, res) => {
  const updated = itemRepository.update(req.item.id, req.body);

  if (!updated) {
    // La ligne existait quand le param converter a tourné,
    // mais a été supprimée entre-temps (condition de concurrence).
    const error = new Error("L'item a été supprimé pendant la requête");
    (error as any).status = 410; // Gone
    throw error;
  }

  res.sendStatus(204);
};

Le gestionnaire global sendErrors lira err.status et l'utilisera comme code de statut HTTP.

Réponses d'erreur et sécurité

StartER garde volontairement les réponses d'erreur concises :

Statut Signification Quoi dire
400 Bad Request Renvoyer les erreurs de validation Zod (sans risque : elles décrivent les champs attendus, pas l'état interne)
401 Unauthorized Ne rien renvoyer (sendStatus(401)). Ne jamais révéler pourquoi l'authentification a échoué
403 Forbidden Ne rien renvoyer (sendStatus(403)). Ne jamais révéler la règle de propriété
404 Not Found Ne rien renvoyer (sendStatus(404)). Ne jamais confirmer si la ressource existe mais est inaccessible
500 Server Error Renvoyer un message générique. Ne jamais exposer les stack traces, erreurs SQL ou chemins internes en production

Warning

Les messages d'erreur détaillés sont un cadeau pour les attaquants. Une réponse 401 qui dit « Signature JWT invalide » indique à l'attaquant qu'il a un JWT et qu'il est presque valide. Un simple 401 ne lui dit rien.

Gestion des erreurs côté React

Les sections précédentes couvrent les erreurs Express (API). Côté React, deux mécanismes gèrent les états de chargement et les échecs des composants.

Suspense : états de chargement

Dans Layout.tsx, le <Outlet /> est enveloppé dans un élément <Suspense> :

<main>
  <Suspense fallback={<p>Loading…</p>}>
    <Outlet />
  </Suspense>
</main>

Quand un composant appelle use(getOrFetch("/api/something")), React suspend l'affichage du composant. L'élément <Suspense> intercepte cet état de suspension et affiche le fallback pendant le chargement des données. L'en-tête et la navigation restent visibles pendant ce temps.

Note

C'est une enveloppe unique au niveau du <Outlet>. Les composants individuels n'ont pas besoin de leur propre <Suspense>. Ajouter des éléments <Suspense> plus granulaires est possible mais pas nécessaire pour un projet starter.

Error boundary : route sans chemin (pathless wrapper)

Dans routes.tsx, toutes les routes enfants sont enveloppées dans une route sans chemin avec un errorElement :

children: [
  {
    // Route sans chemin : error boundary pour toutes les pages.
    // React Router rend <Outlet /> par défaut (pas besoin de Component).
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <Home /> },
      // ...autres routes
    ],
  },
]

Quand un composant lève une erreur (par ex. getOrFetch("/api/something") échoue à cause d'une erreur réseau), cette error boundary l'intercepte et affiche ErrorPage à l'intérieur du Layout. La navigation reste visible et l'utilisateur peut revenir en arrière : pas de crash de page complet.

Note

La route racine a aussi son propre errorElement comme filet de sécurité ultime. Il intercepte les erreurs qui surviennent avant le rendu du Layout (par ex. échecs du loader). La route sans chemin gère les erreurs à l'intérieur du Layout.

Bonnes pratiques et cas d'usage

  • Laissez les middlewares faire le travail : les validateurs, param converters et middlewares d'authentification gèrent déjà 80% des scénarios d'erreur. Ne dupliquez pas leur logique dans vos actions.
  • Interceptez spécifiquement, relancez génériquement : n'interceptez que les erreurs que vous savez gérer. Tout le reste doit atteindre le gestionnaire global.
  • Loggez les mutations : envisagez d'ajouter des console.info pour les mutations réussies (POST, PUT, DELETE) afin de faciliter le débogage et l'audit.
  • Testez les cas d'erreur : dans vos contrats, définissez toujours les scénarios bad_request, unauthorized, forbidden et not_found. Ce sont les erreurs que vos utilisateurs rencontreront réellement.

Voir aussi

Clone this wiki locally