1. Concept et fonctionnement des middlewares
Définition et rôle des middlewares
Les middlewares sont l'un des concepts les plus puissants et fondamentaux d'ExpressJS. Un middleware est une fonction qui a accès aux objets de requête (req), de réponse (res), et à la fonction suivante dans le cycle de requête-réponse de l'application, généralement appelée next
.
Les middlewares peuvent :
- Exécuter n'importe quel code
- Modifier les objets de requête et de réponse
- Terminer le cycle de requête-réponse
- Appeler le prochain middleware dans la pile
La structure d'une fonction middleware est la suivante :
1function monMiddleware(req, res, next) { 2 // Code du middleware 3 4 // Appel du middleware suivant 5 next(); 6}
Les middlewares sont au cœur de la philosophie d'Express, qui peut être considéré comme une série d'appels de fonctions middleware.
Cycle de vie d'une requête
Lorsqu'une requête arrive sur un serveur Express, elle traverse une série de middlewares avant de recevoir une réponse. Ce processus peut être visualisé comme une "pile" ou une "chaîne" de middlewares.
Voici les étapes typiques du cycle de vie d'une requête :
- Réception de la requête : Express reçoit la requête HTTP
- Middlewares d'application : La requête traverse les middlewares globaux (définis avec
app.use()
) - Middlewares de routeur : Si la requête correspond à un routeur, elle traverse ses middlewares
- Middlewares de route : Si la requête correspond à une route spécifique, elle traverse ses middlewares
- Gestionnaire de route : La fonction finale qui génère une réponse
- Middlewares d'erreur : Si une erreur est générée, elle est traitée par les middlewares d'erreur
- Envoi de la réponse : La réponse est envoyée au client
Si un middleware ne termine pas le cycle de requête-réponse (en appelant res.send()
, res.json()
, etc.), il doit appeler next()
pour passer au middleware suivant, sinon la requête restera en suspens.
Ordre d'exécution des middlewares
L'ordre dans lequel les middlewares sont définis est crucial, car ils sont exécutés séquentiellement. Les middlewares définis en premier sont exécutés en premier.
1const express = require('express'); 2const app = express(); 3 4// Premier middleware - s'exécute pour toutes les requêtes 5app.use((req, res, next) => { 6 console.log('Middleware 1'); 7 next(); 8}); 9 10// Deuxième middleware - s'exécute pour toutes les requêtes 11app.use((req, res, next) => { 12 console.log('Middleware 2'); 13 next(); 14}); 15 16// Route spécifique avec son propre middleware 17app.get('/', (req, res, next) => { 18 console.log('Middleware de route'); 19 res.send('Hello World'); 20}); 21 22// Pour une requête GET à '/', la console affichera : 23// Middleware 1 24// Middleware 2 25// Middleware de route
Les middlewares peuvent également être limités à des chemins spécifiques :
1// Ce middleware ne s'applique qu'aux requêtes commençant par /api 2app.use('/api', (req, res, next) => { 3 console.log('API Middleware'); 4 next(); 5});
Passage au middleware suivant (next())
La fonction next()
est un paramètre fourni aux fonctions middleware et, lorsqu'elle est appelée, exécute le middleware suivant dans la pile. Si next()
n'est pas appelé, la requête reste en suspens et le client n'obtient jamais de réponse.
next()
peut être appelé de différentes manières :
- Appel simple :
next()
- Passe au middleware suivant - Avec une erreur :
next(err)
- Saute tous les middlewares restants et passe à un middleware de gestion d'erreur - Avec 'route' :
next('route')
- Saute le reste des middlewares dans la route actuelle et passe à la route suivante
Exemple d'utilisation de next('route')
:
1app.get('/utilisateurs/:id', 2 // Premier middleware 3 (req, res, next) => { 4 // Si l'ID est 0, passer à la route suivante 5 if (req.params.id === '0') { 6 next('route'); 7 } else { 8 next(); 9 } 10 }, 11 // Deuxième middleware (ignoré si ID est 0) 12 (req, res, next) => { 13 res.send('Utilisateur régulier'); 14 } 15); 16 17// Route suivante (exécutée si ID est 0) 18app.get('/utilisateurs/:id', (req, res) => { 19 res.send('Utilisateur spécial'); 20});
2. Middlewares intégrés
Express fournit plusieurs middlewares intégrés qui simplifient les tâches courantes.
express.json()
Ce middleware analyse les corps de requête JSON entrants et les rend disponibles dans req.body
. Il est basé sur body-parser
.
1const express = require('express'); 2const app = express(); 3 4// Analyse les requêtes avec Content-Type: application/json 5app.use(express.json()); 6 7app.post('/api/data', (req, res) => { 8 console.log(req.body); // Contient les données JSON parsées 9 res.json({ reçu: true }); 10});
Options configurables :
limit
: Contrôle la taille maximale du corps de requête (par défaut '100kb')strict
: N'accepte que les tableaux et objets (par défaut true)reviver
: Fonction passée à JSON.parse()
express.urlencoded()
Ce middleware analyse les corps de requête URL-encoded (typiquement envoyés par les formulaires HTML) et les rend disponibles dans req.body
.
1// Analyse les requêtes avec Content-Type: application/x-www-form-urlencoded 2app.use(express.urlencoded({ extended: false })); 3 4app.post('/formulaire', (req, res) => { 5 console.log(req.body); // Contient les données du formulaire 6 res.send('Formulaire reçu'); 7});
Options importantes :
extended
: Utilise la bibliothèque qs (true) ou querystring (false) pour l'analyselimit
: Contrôle la taille maximale du corps de requête
express.static()
Ce middleware sert des fichiers statiques comme des images, des fichiers CSS et JavaScript.
1// Sert les fichiers statiques du répertoire 'public' 2app.use(express.static('public')); 3 4// Avec un préfixe de chemin 5app.use('/static', express.static('public')); 6// Accessible via /static/css/style.css pour le fichier public/css/style.css
Options utiles :
dotfiles
: Comment gérer les fichiers commençant par un point (par défaut 'ignore')etag
: Activer ou désactiver la génération d'etag (par défaut true)extensions
: Définir les extensions de secours (par défaut false)index
: Envoyer un fichier d'index si un répertoire est demandé (par défaut 'index.html')maxAge
: Définir l'en-tête Cache-Control en millisecondes ou une chaîne (par défaut 0)
Autres middlewares intégrés
Express 4.x et 5.x incluent d'autres middlewares utiles :
- express.raw() : Analyse les corps de requête bruts dans un Buffer
- express.text() : Analyse les corps de requête texte
- express.Router() : Crée un nouvel objet routeur
3. Middlewares tiers populaires
L'écosystème Express comprend de nombreux middlewares tiers qui étendent ses fonctionnalités.
morgan (logging)
Morgan est un middleware de logging HTTP qui enregistre les détails des requêtes.
1const express = require('express'); 2const morgan = require('morgan'); 3const app = express(); 4 5// Utilisation de morgan avec le format prédéfini 'dev' 6app.use(morgan('dev')); 7// Exemple de sortie: GET / 200 7.957 ms - 13 8 9// Formats prédéfinis disponibles : combined, common, dev, short, tiny
Morgan peut également être configuré pour écrire dans un fichier :
1const fs = require('fs'); 2const path = require('path'); 3 4// Création d'un flux d'écriture 5const accessLogStream = fs.createWriteStream( 6 path.join(__dirname, 'access.log'), 7 { flags: 'a' } 8); 9 10// Configuration de morgan pour écrire dans ce flux 11app.use(morgan('combined', { stream: accessLogStream }));
cors (Cross-Origin Resource Sharing)
Le middleware CORS permet de gérer le partage de ressources entre origines multiples.
1const express = require('express'); 2const cors = require('cors'); 3const app = express(); 4 5// Activer CORS pour toutes les routes 6app.use(cors()); 7 8// Configuration avancée 9app.use(cors({ 10 origin: 'https://example.com', // Autoriser seulement cette origine 11 methods: ['GET', 'POST'], // Méthodes autorisées 12 allowedHeaders: ['Content-Type', 'Authorization'], // En-têtes autorisés 13 credentials: true // Autoriser les cookies 14})); 15 16// CORS pour une route spécifique 17app.get('/api/public', cors(), (req, res) => { 18 res.json({ message: 'Cette API est accessible de partout' }); 19});
compression (compression de réponses)
Ce middleware compresse les réponses envoyées aux clients, réduisant la taille des données transmises.
1const express = require('express'); 2const compression = require('compression'); 3const app = express(); 4 5// Compression de base 6app.use(compression()); 7 8// Configuration avancée 9app.use(compression({ 10 level: 6, // Niveau de compression (0-9, par défaut 6) 11 threshold: 1024, // Seuil minimal en octets pour la compression 12 filter: (req, res) => { // Fonction pour déterminer quand compresser 13 if (req.headers['x-no-compression']) { 14 return false; // Ne pas compresser si cet en-tête est présent 15 } 16 return compression.filter(req, res); // Utiliser le filtre par défaut 17 } 18}));
4. Création de middlewares personnalisés
Créer vos propres middlewares vous permet d'encapsuler des fonctionnalités spécifiques à votre application.
Structure d'un middleware
Un middleware est une fonction qui prend trois paramètres : req
, res
, et next
.
1function monMiddleware(req, res, next) { 2 // 1. Exécuter du code 3 console.log('Middleware exécuté à', new Date().toISOString()); 4 5 // 2. Modifier les objets req et res 6 req.horodatage = Date.now(); 7 8 // 3. Terminer le cycle de requête-réponse OU 9 // if (condition) { 10 // return res.status(401).send('Non autorisé'); 11 // } 12 13 // 4. Appeler le prochain middleware 14 next(); 15} 16 17// Utilisation 18app.use(monMiddleware);
Middlewares d'application
Les middlewares d'application sont montés sur l'instance de l'application avec app.use()
et s'appliquent à toutes les requêtes ou à celles correspondant à un chemin spécifique.
1// Middleware de logging personnalisé 2app.use((req, res, next) => { 3 console.log(`${new Date().toISOString()} ${req.method} ${req.url}`); 4 next(); 5}); 6 7// Middleware pour un chemin spécifique 8app.use('/api', (req, res, next) => { 9 console.log('Requête API reçue'); 10 next(); 11});
Middlewares de routeur
Les middlewares de routeur fonctionnent de la même manière que les middlewares d'application, mais sont liés à une instance de express.Router()
.
1const express = require('express'); 2const router = express.Router(); 3 4// Middleware spécifique au routeur 5router.use((req, res, next) => { 6 console.log('Routeur utilisateurs accédé'); 7 next(); 8}); 9 10router.get('/', (req, res) => { 11 res.send('Liste des utilisateurs'); 12}); 13 14// Montage du routeur 15app.use('/utilisateurs', router);
Middlewares d'erreur
Les middlewares d'erreur sont spéciaux car ils prennent quatre paramètres : err
, req
, res
, et next
.
1// Middleware de gestion d'erreur 2app.use((err, req, res, next) => { 3 console.error(err.stack); 4 res.status(500).send('Quelque chose s\'est mal passé!'); 5});
Pour qu'un middleware d'erreur soit appelé, un autre middleware doit passer une erreur à next()
:
1app.get('/problematique', (req, res, next) => { 2 try { 3 // Code qui pourrait générer une erreur 4 const resultat = fonctionProblematique(); 5 res.send(resultat); 6 } catch (err) { 7 next(err); // Passe l'erreur au middleware d'erreur 8 } 9}); 10 11// Ou plus simplement 12app.get('/problematique', (req, res, next) => { 13 // Si next() est appelé avec un argument, Express le traite comme une erreur 14 next(new Error('Quelque chose s\'est mal passé')); 15});
Exercices pratiques
Exercice 1: Créer un middleware de logging personnalisé
Créez un middleware qui enregistre les détails de chaque requête (méthode, URL, heure, adresse IP) dans un fichier de log.
Exercice 2: Implémenter un middleware d'authentification simple
Créez un middleware qui vérifie si une requête contient un token valide dans les en-têtes et refuse l'accès si ce n'est pas le cas.
Exercice 3: Intégrer plusieurs middlewares tiers
Configurez une application Express avec plusieurs middlewares tiers (morgan, cors, compression) et assurez-vous qu'ils sont exécutés dans le bon ordre.