
Middlewares : traitement des requêtes en amont
Comprenez le concept fondamental des middlewares dans Express.js. Apprenez comment ils interceptent, traitent les requetes/reponses et utilisent `next()` pour chainer les operations.
Introduction : le chainon manquant entre la requete et la route
Lorsque nous définissons des gestionnaires de routes avec `app.get()`, `app.post()`, etc., nous écrivons le code qui génère la réponse finale pour cette route spécifique. Cependant, de nombreuses tâches doivent souvent être effectuées avant que le gestionnaire de route final ne soit atteint, ou même de manière transversale à plusieurs routes. Pensez à la journalisation (logging) de chaque requête, à la vérification de l'authentification d'un utilisateur, à la validation des données entrantes, au parsing du corps d'une requête POST, ou à la compression des réponses.
Répéter ce code dans chaque gestionnaire de route serait inefficace et difficile à maintenir. C'est là qu'intervient l'un des concepts les plus puissants et centraux d'Express.js : les middlewares.
Un middleware, dans le contexte d'Express, est essentiellement une fonction qui a accès aux objets requête (`req`), réponse (`res`), et à une fonction spéciale nommée `next`. Cette fonction `next` est la clé qui permet de passer le contrôle au middleware suivant dans la chaîne de traitement. Les middlewares agissent comme des "maillons" dans une chaîne : une requête entrante passe à travers une série de middlewares avant d'atteindre (potentiellement) le gestionnaire de route final. Chaque middleware peut inspecter ou modifier la requête/réponse, terminer le cycle requête-réponse, ou passer la main au suivant.
Comment fonctionne un middleware : la signature `(req, res, next)`
La forme la plus courante d'une fonction middleware Express respecte la signature suivante :
function myMiddleware(req, res, next) {
// Logique du middleware...
next(); // Appeler next() pour passer au middleware/handler suivant
}- `req` : L'objet requête (comme dans les gestionnaires de routes). Le middleware peut lire ou même ajouter des informations à cet objet (par exemple, ajouter les données utilisateur après authentification : `req.user = ...`).
- `res` : L'objet réponse (comme dans les gestionnaires de routes). Le middleware peut interagir avec la réponse, par exemple en définissant des en-têtes communs.
- `next` : C'est une fonction de rappel qui, lorsqu'elle est appelée, passe le contrôle au middleware suivant dans la pile défini pour la route actuelle. Si le middleware actuel est le dernier de sa chaîne (avant le gestionnaire de route final), `next()` passe le contrôle au gestionnaire de route.
Le rôle de `next()` est absolument crucial. Si un middleware ne termine pas le cycle requête-réponse (en appelant `res.send()`, `res.json()`, etc.) et n'appelle pas non plus `next()`, la requête restera "bloquée" et le client n'obtiendra jamais de réponse, finissant par un timeout. Appeler `next()` est la manière de dire : "J'ai fini mon travail ici, passe la main au prochain".
Il est également possible d'appeler `next()` avec un argument d'erreur (par exemple, `next(new Error('Authentification échouée'))`). Dans ce cas, Express sautera tous les middlewares et gestionnaires de route restants (sauf les middlewares de gestion d'erreur) et passera directement au gestionnaire d'erreur défini pour l'application.
Les middlewares sont exécutés séquentiellement dans l'ordre où ils sont définis ou utilisés par l'application ou le routeur. Cet ordre est donc très important.
Types de middlewares et exemples d'utilisation
Les middlewares peuvent être classés selon leur portée et leur origine :
- Middleware au niveau de l'application : Lié à l'instance `app` avec `app.use()` ou `app.METHOD()`. S'applique à toutes les requêtes si aucun chemin n'est spécifié, ou aux requêtes correspondant au chemin s'il est fourni.
- Middleware au niveau du routeur : Lié à une instance de `express.Router()`. Similaire au niveau application mais limité aux routes définies par ce routeur.
- Middleware de gestion d'erreur : A une signature spéciale `(err, req, res, next)` et est appelé lorsque `next(err)` est invoqué.
- Middleware intégré : Fourni par Express lui-même (ex: `express.json()`, `express.urlencoded()`, `express.static()`).
- Middleware tiers : Installé via npm/yarn (ex: `cors`, `helmet`, `morgan`, `passport`).
Exemple 1 : Middleware de logging simple (niveau application)
Ce middleware loggue la méthode et l'URL de chaque requête entrante.
const express = require('express');
const app = express();
// Middleware de logging
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.originalUrl}`);
next(); // Passer au middleware/handler suivant
});
app.get('/', (req, res) => {
res.send('Accueil');
});
app.listen(3000);
// Chaque requête affichera une ligne dans la console grâce au middleware.
Ici, `app.use()` sans chemin spécifié applique le middleware à toutes les routes définies *après* lui.
Exemple 2 : Middlewares intégrés pour parser le corps des requêtes
Pour pouvoir lire `req.body` dans vos gestionnaires de routes (par exemple, pour des requêtes POST avec du JSON), vous devez utiliser les middlewares intégrés appropriés :
const express = require('express');
const app = express();
// Middleware pour parser le JSON dans req.body
app.use(express.json());
// Middleware pour parser les données de formulaire URL-encodées dans req.body
app.use(express.urlencoded({ extended: true }));
app.post('/api/data', (req, res) => {
// Grâce aux middlewares ci-dessus, req.body est maintenant disponible
console.log('Données reçues:', req.body);
res.json({ received: req.body });
});
app.listen(3000);
Un autre middleware intégré très courant est `express.static('public')` qui sert automatiquement les fichiers statiques (CSS, JS, images) depuis un répertoire spécifié (ici, `public`).
Exemple 3 : Middleware spécifique à une route
Vous pouvez appliquer un middleware uniquement à certaines routes en le passant comme argument avant le gestionnaire final.
function checkApiKey(req, res, next) {
const apiKey = req.query.apiKey; // Exemple simple: clé API dans l'URL
if (apiKey === 'maSuperCleSecrete') {
next(); // Clé valide, continuer
} else {
res.status(401).send('Clé API invalide ou manquante'); // Bloquer
}
}
// Appliquer le middleware checkApiKey uniquement à cette route
app.get('/api/protected-resource', checkApiKey, (req, res) => {
res.json({ data: 'Informations secrètes !' });
});
app.get('/api/public-resource', (req, res) => {
res.json({ data: 'Informations publiques.'}); // Pas besoin de clé API ici
});
Exemple 4 : Utilisation d'un middleware tiers (ex: `cors`)
Pour autoriser les requêtes depuis d'autres origines (Cross-Origin Resource Sharing), on utilise souvent le middleware `cors`.
npm install corsconst express = require('express');
const cors = require('cors'); // Importer le middleware
const app = express();
app.use(cors()); // Appliquer CORS à toutes les routes
app.get('/api/some-data', (req, res) => {
res.json({ message: 'Données accessibles depuis d\'autres origines' });
});
app.listen(3000);
Gestion centralisee des erreurs avec les middlewares
Express permet de définir un type spécial de middleware pour gérer les erreurs de manière centralisée. Ces middlewares ont une signature spécifique avec quatre arguments : `(err, req, res, next)`. Le premier argument `err` contient l'objet d'erreur.
Pour qu'un middleware de gestion d'erreur soit appelé, une erreur doit être passée à la fonction `next()` depuis un middleware ou un gestionnaire de route précédent (par exemple, `next(error)`), ou une erreur doit être levée de manière synchrone dans un gestionnaire de route (bien que `next(error)` soit préférable).
Le middleware de gestion d'erreur doit être défini après tous les autres `app.use()` et toutes les définitions de routes.
// ... (autres middlewares et routes) ...
// Middleware pour gérer les erreurs 404 (après toutes les routes)
app.use((req, res, next) => {
res.status(404).send('Désolé, page introuvable !');
});
// Middleware de gestion d'erreur (doit avoir 4 arguments)
app.use((err, req, res, next) => {
console.error('--- Erreur détectée ---');
console.error(err.stack); // Logger la pile d'erreurs côté serveur
// Envoyer une réponse d'erreur générique au client
res.status(err.status || 500).json({
error: {
message: err.message || 'Une erreur interne est survenue.'
// Ne pas exposer err.stack ou trop de détails en production
}
});
});
// Exemple de route qui peut générer une erreur
app.get('/cause-error', (req, res, next) => {
try {
// Simuler une erreur
throw new Error('Oups, quelque chose a cassé !');
} catch (error) {
next(error); // Passer l'erreur au gestionnaire d'erreur
}
});
Cette approche permet de centraliser la logique de logging des erreurs et la manière dont les réponses d'erreur sont formatées pour le client.
Conclusion : la puissance et la flexibilite des middlewares
Les middlewares sont le coeur battant d'Express.js, lui conférant sa flexibilité et sa modularité. Ils permettent de décomposer la logique de traitement des requêtes en petites unités réutilisables, chacune responsable d'une tâche spécifique (logging, authentification, parsing, validation, etc.).
En comprenant le flux d'exécution à travers la chaîne de middlewares et le rôle essentiel de la fonction `next()`, vous pouvez construire des applications web et des API bien structurées, maintenables et robustes. L'écosystème riche en middlewares tiers vous permet également d'intégrer rapidement des fonctionnalités complexes sans avoir à réinventer la roue.