1. Qu'est-ce qu'un middleware ?
Un middleware est une fonction qui s'exécute au milieu de la chaîne de traitement d'une requête, entre sa réception et l'envoi de la réponse. C'est l'un des concepts centraux d'Express.
Signature d'un middleware
// Middleware standard : 3 paramètres
function monMiddleware(req, res, next) {
// req : requête entrante
// res : réponse sortante
// next : fonction pour passer au middleware suivant
console.log('Je suis un middleware !');
// OBLIGATOIRE : appeler next() ou envoyer une réponse
// Si on n'appelle pas next(), la chaîne s'arrête ici
next();
}
// Middleware d'erreur : 4 paramètres (doit être en dernier !)
function errorMiddleware(err, req, res, next) {
console.error(err.message);
res.status(500).json({ error: err.message });
}
Flux d'exécution
const express = require('express');
const app = express();
// Requête → MW1 → MW2 → Route Handler → Réponse
app.use((req, res, next) => {
console.log('MW1 : avant');
next(); // Passe au suivant
console.log('MW1 : après'); // Exécuté APRÈS la réponse
});
app.use((req, res, next) => {
console.log('MW2 : exécuté');
req.monDonnee = 'valeur ajoutée par MW2'; // Enrichir req
next();
});
app.get('/', (req, res) => {
console.log('Handler route');
res.json({ data: req.monDonnee }); // → { data: "valeur ajoutée par MW2" }
});
app.listen(3000);
next(err) — Passer une erreur
app.use((req, res, next) => {
try {
// Si une erreur survient...
if (!req.headers.authorization) {
throw new Error('Token manquant');
}
next(); // Tout va bien
} catch (err) {
next(err); // Passer l'erreur au error handler
}
});
2. app.use() et middlewares intégrés
app.use() monte un middleware global ou sur un préfixe de chemin.
Express embarque plusieurs middlewares très utiles directement.
Middlewares intégrés à Express
const express = require('express');
const path = require('path');
const app = express();
// ── express.json() ─────────────────────────────────────
// Parse les body JSON → disponible dans req.body
// Content-Type doit être : application/json
app.use(express.json());
// ── express.urlencoded() ───────────────────────────────
// Parse les body de formulaires HTML
// Content-Type : application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));
// ── express.static() ───────────────────────────────────
// Sert les fichiers statiques d'un dossier
// GET /style.css → lit public/style.css
app.use(express.static(path.join(__dirname, 'public')));
// ── express.static() avec préfixe ─────────────────────
// GET /assets/logo.png → lit images/logo.png
app.use('/assets', express.static(path.join(__dirname, 'images')));
app.listen(3000);
app.use() avec préfixe de chemin
// Middleware uniquement pour les routes /api/...
app.use('/api', (req, res, next) => {
console.log('Requête vers /api :', req.url);
next();
});
// Monter un router sur un préfixe (voir module N07)
// const usersRouter = require('./routes/users');
// app.use('/api/users', usersRouter);
Ordre : middlewares AVANT routes
// ✅ CORRECT — express.json() avant la route qui utilise req.body
app.use(express.json());
app.post('/data', (req, res) => {
console.log(req.body); // { name: "Alice" }
res.json({ received: req.body });
});
// ❌ INCORRECT — express.json() après la route
// app.post('/data', handler);
// app.use(express.json()); // req.body sera undefined dans handler !
Content-Type: application/json.
Pour les formulaires HTML, utilisez express.urlencoded().
3. Middlewares custom
Vous pouvez créer vos propres middlewares pour factoriser des comportements transversaux : logging, timing, authentification, validation, etc.
Middleware 1 — Logger de requêtes
// logger.js — Middleware de logging
const logger = (req, res, next) => {
const start = Date.now();
const timestamp = new Date().toISOString();
// On écoute 'finish' pour logger APRÈS la réponse
res.on('finish', () => {
const duration = Date.now() - start;
const color = res.statusCode < 400 ? '\x1b[32m' : '\x1b[31m'; // vert/rouge
console.log(
`${timestamp} ${color}${req.method}\x1b[0m ${req.path} → ${res.statusCode} (${duration}ms)`
);
});
next(); // Ne pas oublier !
};
module.exports = logger;
Middleware 2 — Timer (req.startTime)
// Ajouter une propriété personnalisée à req
const timer = (req, res, next) => {
req.startTime = Date.now(); // Enrichit l'objet req
// Les handlers suivants peuvent utiliser req.startTime
next();
};
// Utilisation dans une route
app.use(timer);
app.get('/slow', (req, res) => {
const elapsed = Date.now() - req.startTime;
res.json({ elapsed: elapsed + 'ms', message: 'Réponse rapide' });
});
Middleware 3 — Validation de header
// Vérifier la présence d'un header requis
const requireContentType = (req, res, next) => {
// Uniquement pour les requêtes avec body (POST, PUT, PATCH)
if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
const ct = req.headers['content-type'] ?? '';
if (!ct.includes('application/json')) {
return res.status(415).json({
error: 'Content-Type: application/json requis',
});
}
}
next();
};
app.use(express.json());
app.use(requireContentType);
app.post('/users', (req, res) => {
res.status(201).json({ created: req.body });
});
req (comme req.user, req.startTime),
vous pouvez partager des données entre middlewares et handlers de façon propre.
4. Middleware d'erreur
Le middleware d'erreur a une signature spéciale à 4 paramètres :
(err, req, res, next). Il doit être déclaré en dernier, après toutes les routes.
Déclenchement avec next(err)
const express = require('express');
const app = express();
app.use(express.json());
// Route qui peut déclencher une erreur
app.get('/users/:id', (req, res, next) => {
const id = parseInt(req.params.id);
if (isNaN(id)) {
// next(err) déclenche le error handler
return next(new Error('ID invalide — doit être un nombre'));
}
const user = { id, name: 'Alice' };
res.json({ data: user });
});
// Erreur dans un handler async
app.get('/async-route', async (req, res, next) => {
try {
// Simuler une erreur asynchrone
throw new Error('Erreur asynchrone !');
} catch (err) {
next(err); // Toujours passer l'erreur à next()
}
});
Error handler centralisé complet
// Classe d'erreur personnalisée
class AppError extends Error {
constructor(message, status = 500) {
super(message);
this.status = status;
this.name = 'AppError';
}
}
// Handler 404 — toutes les routes non trouvées
app.use('*', (req, res, next) => {
next(new AppError(`Route "${req.path}" introuvable`, 404));
});
// Error handler — DOIT être en dernier avec 4 paramètres
app.use((err, req, res, next) => {
const status = err.status || 500;
const message = err.message || 'Erreur interne du serveur';
// Logger l'erreur côté serveur
if (status >= 500) {
console.error('\x1b[31mERREUR 500:\x1b[0m', err.stack);
}
res.status(status).json({
success: false,
error: {
message,
status,
// Ne pas exposer le stack en production
...(process.env.NODE_ENV !== 'production' && { stack: err.stack }),
},
});
});
// Utilisation
app.get('/secure', (req, res, next) => {
if (!req.headers.authorization) {
return next(new AppError('Non autorisé', 401));
}
res.json({ data: 'Données sécurisées' });
});
app.listen(3000);
err ou next, Express le traitera comme un middleware normal
et les erreurs ne seront pas interceptées.
5. Middlewares tiers
L'écosystème npm propose de nombreux middlewares Express prêts à l'emploi.
Les plus utilisés sont morgan (logging) et cors (CORS).
morgan — Logging HTTP
// Installation : npm install morgan
const express = require('express');
const morgan = require('morgan');
const app = express();
// Formats prédéfinis
app.use(morgan('dev')); // Format coloré pour le développement
// app.use(morgan('tiny')); // Format minimal
// app.use(morgan('combined')); // Format Apache standard (prod)
// Format personnalisé
app.use(morgan(':method :url :status :response-time ms'));
// Morgan avec condition (skip en production par ex.)
app.use(morgan('dev', {
skip: (req) => req.url === '/health', // Ignorer la route health
}));
app.get('/users', (req, res) => res.json({ data: [] }));
app.listen(3000);
cors — Cross-Origin Resource Sharing
// Installation : npm install cors
const express = require('express');
const cors = require('cors');
const app = express();
// Autoriser toutes les origines (déconseillé en prod)
app.use(cors());
// Configuration précise pour la production
app.use(cors({
origin: ['https://monsite.com', 'https://admin.monsite.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // Autoriser les cookies
}));
// CORS sur une seule route
app.get('/public', cors(), (req, res) => {
res.json({ data: 'Accessible depuis partout' });
});
Serveur complet avec morgan + cors
const express = require('express');
const morgan = require('morgan');
const cors = require('cors');
const app = express();
// ── Stack de middlewares ──────────────────────────────
app.use(morgan('dev')); // Logging
app.use(cors()); // CORS
app.use(express.json()); // Body parser
// ── Routes ───────────────────────────────────────────
app.get('/', (req, res) => {
res.json({ status: 'OK', message: 'API opérationnelle' });
});
app.post('/echo', (req, res) => {
res.json({ received: req.body, timestamp: new Date().toISOString() });
});
// ── 404 ──────────────────────────────────────────────
app.use('*', (req, res) => {
res.status(404).json({ error: 'Route introuvable', path: req.path });
});
// ── Error handler ─────────────────────────────────────
app.use((err, req, res, next) => {
res.status(err.status || 500).json({ error: err.message });
});
app.listen(3000, () => console.log('Serveur avec morgan + cors sur :3000'));
npm install helmet) est un autre middleware essentiel
en production : il sécurise les headers HTTP (CSP, HSTS, X-Frame-Options...).
Il sera détaillé dans le module N11 — Sécurité & Production.