Terminez d'abord "Leçon 4 : Gestion des données de requête et création d'endpoints CRUD" pour débloquer cette leçon.Aller à la leçon précédente

Introduction

Chaque API reçoit des données qu'elle n'a pas créées. Les utilisateurs envoient du JSON mal formé, des champs manquants, des chaînes de caractères là où il faudrait des nombres, et des payloads techniquement valides mais logiquement incorrects. Sans couche de validation, ces problèmes corrompent silencieusement votre base de données ou déclenchent des erreurs 500 cryptiques. Sans couche de gestion des erreurs cohérente, chaque route réagit différemment en cas de problème — certaines renvoient de simples chaînes, d'autres des pages d'erreur HTML, d'autres encore ne renvoient rien du tout.

Cette leçon construit ces deux couches : la validation des requêtes à l'aide de la bibliothèque express-validator, et un gestionnaire d'erreurs centralisé qui donne à chaque réponse d'erreur la même forme prévisible.

Validation des données de requête entrantes

express-validator encapsule la bibliothèque éprouvée validator.js et se branche directement en tant que middleware Express. Installez-la dans votre projet :

bash
npm install express-validator

Les règles de validation sont définies comme un tableau de middlewares et attachées à une route avant l'exécution du handler. Voici une route POST /users qui exige un e-mail valide et un mot de passe d'au moins huit caractères :

javascript
const { body, validationResult } = require('express-validator');
const createUserRules = [
body('email').isEmail().withMessage('Must be a valid email address'),
body('password')
.isLength({ min: 8 })
.withMessage('Password must be at least 8 characters'),
];
router.post('/users', createUserRules, (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
// safe to use req.body here
res.status(201).json({ message: 'User created' });
});

L'appel à validationResult(req) collecte toutes les violations trouvées par la chaîne de règles. Si le tableau n'est pas vide, la requête est immédiatement rejetée avec le statut HTTP 422 Unprocessable Entity — le statut sémantiquement correct lorsque le serveur comprend la requête mais ne peut pas la traiter car les données sont invalides.

Diagramme montrant le flux de la requête : requête POST entrante -> chaîne de middlewares de validation -> vérification validationResult -> en cas d'erreurs, retour d'une réponse 422, en l'absence d'erreurs, poursuite vers le handler de la route

Vous pouvez extraire les règles de validation dans un fichier séparé à mesure que le nombre de routes augmente, afin de garder les fichiers de routes concentrés uniquement sur la logique métier.

Construction d'un gestionnaire d'erreurs centralisé

Express dispose d'une signature de middleware spéciale pour la gestion des erreurs : quatre paramètres au lieu de trois. Tout middleware ou route qui appelle next(err) ou qui lève une exception à l'intérieur d'un wrapper async ignore les middlewares classiques et atterrit ici.

Créez un fichier dédié :

javascript
// middleware/errorHandler.js
function errorHandler(err, req, res, next) {
const statusCode = err.statusCode || 500;
const message = err.message || 'Internal Server Error';
res.status(statusCode).json({
error: {
message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
},
});
}
module.exports = errorHandler;

Enregistrez-le après toutes vos routes dans le fichier principal de l'application. L'ordre est important — Express n'invoque ce handler que s'il est défini en dernier :

javascript
const errorHandler = require('./middleware/errorHandler');
app.use('/api', router);
app.use(errorHandler); // must come after all routes

Désormais, n'importe quelle route peut signaler un problème en attachant un statusCode à un objet Error et en le passant à next :

javascript
router.get('/users/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
const err = new Error('User not found');
err.statusCode = 404;
return next(err);
}
res.json(user);
} catch (error) {
next(error); // unexpected errors get 500
}
});

Chaque erreur, attendue ou non, passe désormais par le même handler et renvoie la même forme JSON. Les équipes frontend peuvent écrire une seule fonction de parsing d'erreur au lieu de deviner ce que chaque endpoint renvoie.

Création d'une classe AppError personnalisée

Passer des objets Error bruts avec des propriétés statusCode attachées manuellement devient répétitif. Une petite classe personnalisée permet de clarifier cela :

javascript
// utils/AppError.js
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true; // distinguishes expected errors from bugs
}
}
module.exports = AppError;

Le flag isOperational permet à votre gestionnaire d'erreurs de se comporter différemment selon qu'il s'agit d'erreurs que vous avez délibérément lancées ou de plantages d'exécution inattendus :

javascript
const AppError = require('../utils/AppError');
// in a route
if (!user) {
return next(new AppError('User not found', 404));
}

Vous pouvez vérifier isOperational dans le gestionnaire d'erreurs pour décider s'il faut logger une stack trace complète, envoyer une alerte, ou simplement renvoyer le message tel quel.

Diagramme montrant deux chemins d'erreur : erreur opérationnelle (isOperational à true) allant vers une réponse JSON sécurisée, et erreur inattendue (isOperational à false) allant vers une étape de logging/alerte puis une réponse 500 générique

Erreurs fréquentes

Placer le gestionnaire d'erreurs avant vos routes est l'erreur la plus courante — il ne se déclenche jamais parce qu'aucune erreur n'a encore été transmise. Enregistrez-le toujours en dernier.

Utiliser le HTTP 400 pour chaque erreur client est imprécis. Utilisez 400 pour une syntaxe mal formée, 401 quand l'authentification est manquante, 403 quand l'utilisateur est authentifié mais non autorisé, 404 quand une ressource n'existe pas, et 422 quand la structure est valide mais que le contenu échoue aux règles métier.

Exposer les stack traces en production est un vrai risque de sécurité. L'opérateur de décomposition conditionnel dans l'exemple d'errorHandler ci-dessus (process.env.NODE_ENV === 'development') garantit que les stacks n'apparaissent qu'en local.

Oublier d'envelopper les handlers de route async dans un try/catch signifie que les rejets de promesse non gérés contournent entièrement votre gestionnaire d'erreurs. Un petit utilitaire wrapper async permet d'imposer ce pattern dans toutes les routes sans ajouter de boilerplate.

Résumé

La validation avec express-validator bloque les mauvaises données à l'entrée et renvoie un retour structuré au client. Un gestionnaire d'erreurs centralisé avec une forme JSON cohérente rend votre API prévisible pour les consommateurs et plus facile à maintenir à mesure qu'elle grandit. La classe AppError fait le lien entre ces deux préoccupations — elle offre un moyen propre de signaler des échecs attendus depuis n'importe quel endroit de votre code, et laisse un seul handler décider de la réponse à apporter.

Entraînement

Point de contrôle de la leçon

Révisez la leçon et recevez des conseils sur votre travail.

0/1
Répondu · 0/1