Les middlewares dans Express
Certaines responsabilités reviennent dans de nombreuses requêtes sans appartenir directement au domaine de l’application :
- journaliser les requêtes;
- lire un corps JSON;
- vérifier l’identité de l’utilisateur;
- contrôler ses droits;
- valider les données reçues;
- charger une ressource demandée.
On pourrait effectuer chacune de ces opérations directement dans les gestionnaires de route. Mais la même logique serait alors répétée à plusieurs endroits.
Express permet plutôt de placer ces traitements dans des middlewares, c’est-à-dire des étapes intermédiaires exécutées pendant le traitement d’une requête.
Un middleware intercepte une requête, effectue une responsabilité précise, puis décide si le traitement doit continuer.1. Le middleware dans Express :
Section intitulée « 1. Le middleware dans Express : »Un middleware Express est une fonction qui reçoit généralement trois paramètres (req, res, next) :
function journalisation(req, res, next) { console.log(`${req.method} ${req.url}`); next();}On peut ensuite le monter dans l’application :
app.use(journalisation);Les trois paramètres ont des rôles distincts :
reqreprésente la requête;resreprésente la réponse qui sera envoyée au client;nextpermet de transférer le traitement à l’étape suivante.
La requête peut ainsi traverser plusieurs middlewares avant d’atteindre le gestionnaire de route.
Les trois issues d’un middleware
Section intitulée « Les trois issues d’un middleware »À chaque exécution, un middleware doit éventuellement mener à l’une de ces trois issues :
- appeler
next()pour continuer vers l’étape suivante; - produire une réponse avec
res.send(),res.json(),res.end(), etc.; - transmettre une erreur avec
next(err).
Par exemple :
function verifierCle(req, res, next) { if (!req.headers["x-api-key"]) { return res.status(401).json({ erreur: "clé manquante", }); }
next();}Si la clé est absente, le middleware produit lui-même la réponse.
Sinon, next() laisse la requête poursuivre son chemin.
Où monter un middleware ?
Section intitulée « Où monter un middleware ? »Un middleware peut être appliqué à plusieurs niveaux.
Pour toutes les requêtes :
Section intitulée « Pour toutes les requêtes : »app.use(journalisation);Pour un groupe de routes :
Section intitulée « Pour un groupe de routes : »app.use("/admin", authentifier);Pour une seule route :
Section intitulée « Pour une seule route : »app.get("/profil", authentifier, afficherProfil);Les fonctions fournies par Express peuvent elles aussi être des middlewares.
Par exemple :
app.use(express.json());express.json() lit un éventuel corps JSON et rend son contenu disponible dans req.body.
2. L’ordre forme la chaîne de traitement
Section intitulée « 2. L’ordre forme la chaîne de traitement »Considérons cette route :
app.get( "/boites/:id", journalisation, verifierId, chargerBoite, afficherBoite);Pour une requête correspondante, Express essaie les étapes de gauche à droite :
journalisation ↓verifierId ↓chargerBoite ↓afficherBoiteChaque middleware appelle next() lorsqu’il souhaite poursuivre.
Un middleware peut cependant décider de terminer la chaîne en produisant une réponse.
Par exemple :
function verifierId(req, res, next) { if (!idValide(req.params.id)) { return res.status(400).json({ erreur: "identifiant invalide", }); }
next();}Si l’identifiant est invalide, chargerBoite et afficherBoite ne seront jamais exécutés.
3. Les erreurs suivent une autre voie
Section intitulée « 3. Les erreurs suivent une autre voie »Un middleware peut rencontrer une erreur qu’il ne souhaite pas transformer directement en réponse.
Il peut alors appeler :
next(err);Express cherche ensuite un middleware de gestion d’erreurs.
Celui-ci se distingue par quatre paramètres :
app.use((err, req, res, next) => { console.error(err);
res.status(err.status ?? 500).json({ erreur: err.message, });});Ce middleware est généralement déclaré après les routes et les autres middlewares.
Les fonctions asynchrones avec Express 5
Section intitulée « Les fonctions asynchrones avec Express 5 »Avec Express 5, une erreur levée dans un middleware async ou une promesse rejetée est automatiquement transmise au traitement des erreurs.
Par exemple :
app.get("/boites/:id", async (req, res) => { const boite = await Boite.findById(req.params.id);
res.json(boite);});Si findById() échoue en levant une erreur, Express peut transmettre cette erreur au middleware de gestion d’erreurs.
4. Une chaîne d’étapes indépendantes
Section intitulée « 4. Une chaîne d’étapes indépendantes »Le fonctionnement des middlewares peut être compris comme un pipeline de traitement.
Chaque étape :
- reçoit la requête et la réponse;
- accomplit une responsabilité particulière;
- décide ensuite de poursuivre, de répondre ou de signaler une erreur.
Cette organisation rappelle le patron architectural pipe-et-filtre.
Express n’implémente pas nécessairement une forme pure de ce patron : les middlewares partagent notamment les mêmes objets req et res et peuvent interrompre la chaîne.
Cela permet de :
- réutiliser une même étape sur plusieurs routes;
- insérer ou retirer une vérification;
- modifier l’ordre des traitements;
- garder les gestionnaires de route centrés sur leur responsabilité principale.
5. Authentification et autorisation
Section intitulée « 5. Authentification et autorisation »L’authentification et l’autorisation illustrent particulièrement bien cette séparation des responsabilités.
Authentifier consiste à établir l’identité de l’utilisateur.
Autoriser consiste ensuite à déterminer si cet utilisateur possède les droits nécessaires.
Ce sont deux responsabilités différentes.
On peut donc créer deux middlewares :
authentifier ↓exigerRole("admin") ↓gestionnaire5.1 Décrire ce que garantit un middleware
Section intitulée « 5.1 Décrire ce que garantit un middleware »Pour comprendre l’ordre nécessaire, on peut décrire simplement ce que chaque middleware reçoit et ce qu’il garantit.
authentifier
Section intitulée « authentifier »Il recherche une preuve d’identité dans la requête.
S’il appelle next(), il garantit que :
req.utilisateurest défini.
Sinon, il répond 401 Unauthorized.
exigerRole(role)
Section intitulée « exigerRole(role) »Il suppose que :
req.utilisateura déjà été défini.
Il vérifie ensuite si l’utilisateur possède le rôle demandé.
S’il possède ce rôle, le traitement continue.
Sinon, le middleware répond 403 Forbidden.
5.2 Observer l’effet de l’ordre
Section intitulée « 5.2 Observer l’effet de l’ordre »VérificationOn place exigerRole('admin') avant authentifier. La requête contient pourtant un jeton valide appartenant à un administrateur. Quel est le problème ?
exigerRole s’exécute avant que l’identité ait été établie. req.utilisateur n’a donc pas encore été défini. L’utilisateur peut être administrateur, mais le middleware d’autorisation ne dispose pas encore de cette information.
À retenir
Section intitulée « À retenir »Un middleware Express est une étape intermédiaire du traitement d’une requête.
Il peut notamment :
- observer ou modifier
reqetres; - appeler
next()pour poursuivre; - produire directement une réponse;
- transmettre une erreur;
- préparer des informations pour les étapes suivantes.
L’ordre dans lequel les middlewares sont montés détermine leur ordre d’exécution.