Le middleware est une fonction du pipeline de requêtes qui inspecte ou modifie requêtes et réponses avant l’exécution de la logique de votre route. Deux sens partagent le mot — le sens « entreprise », plus ancien (brokers de messages et bus d’intégration entre applications), et le sens framework web que couvre cette entrée : des fonctions à l’intérieur d’une application, que chaque requête traverse dans l’ordre. Express énonce le modèle sans détour : une app « est essentiellement une série d’appels à des fonctions middleware » — et l’ordre de ces appels est, littéralement, le programme.
Points clés
| Question | Réponse |
|---|---|
| Le contrat | Inspecter/modifier → puis répondre (court-circuit) ou appeler next() |
| La forme | Un oignon : les requêtes descendent la pile, les réponses remontent |
| La loi | Ordre d’enregistrement = ordre d’exécution — la plupart des bugs de middleware sont des bugs d’ordre |
| La pile canonique | En-têtes → CORS → parsing → logging → authn → authz → limites → routes → 404 → erreurs |
| vs. la passerelle | Le middleware s’exécute dans une app ; une passerelle se place devant plusieurs |
La pile, dans l’ordre
// JavaScript / Node.js — Express + Parse Server
// Middleware: functions the request flows through, in registration order
const app = express();
app.use(helmet()); // 1 · security headers
app.use(cors(corsOptions)); // 2 · CORS before anything that fails
app.use(express.json({ limit: '1mb' })); // 3 · body parsing, bounded
// Parse Server IS middleware — a whole backend mounted into the stack
app.use('/parse', new ParseServer(config).app);
app.use(notFoundHandler); // 404 — after all routes
app.use(errorHandler); // error handler LAST (4 args) // Flutter / Dart — Back4app Flutter SDK
// One SDK call — and the platform's middleware stack ran the gauntlet:
final response =
await QueryBuilder<ParseObject>(ParseObject('Post')).query();
// Before your query touched data, the request passed through:
// security headers → CORS → body limits → key check → session auth
// → rate limiting → routing → (your beforeFind trigger) → the database
// You wrote none of it — that's the middleware a BaaS runs for you. // iOS / Swift — Back4app Swift SDK
// One SDK call — and the platform's middleware stack ran the gauntlet:
let posts = try await Post.query().find()
// Before your query touched data, the request passed through:
// security headers → CORS → body limits → key check → session auth
// → rate limiting → routing → (your beforeFind trigger) → the database
// You wrote none of it — that's the middleware a BaaS runs for you. // Android / Kotlin — Back4app Android SDK
// One SDK call — and the platform's middleware stack ran the gauntlet:
val posts = ParseQuery.getQuery<ParseObject>("Post").find()
// Before your query touched data, the request passed through:
// security headers → CORS → body limits → key check → session auth
// → rate limiting → routing → (your beforeFind trigger) → the database
// You wrote none of it — that's the middleware a BaaS runs for you. Chaque position a sa raison d’être : les en-têtes de sécurité en premier (ils doivent figurer sur chaque réponse, erreurs comprises) ; CORS avant tout ce qui peut échouer (sinon les navigateurs masquent la vraie erreur) ; le parsing du body, borné, avant les routes (sinon req.body vaut undefined) ; l’authentification avant l’autorisation (des permissions vérifiées pour personne sont des permissions accordées à n’importe qui) ; le rate limiting (limitation de débit) avant le travail coûteux (un limiteur placé après la requête en base ne protège rien) ; le 404 après toutes les routes ; le gestionnaire d’erreurs tout en dernier.
L’oignon, dessiné correctement
La moitié que la plupart des explications omettent : le pipeline fonctionne dans les deux sens. La documentation de Django le représente comme un oignon — chaque middleware est une couche autour de la vue, au cœur — et le code situé après l’appel à next() (ou après get_response) s’exécute sur le chemin de retour de la réponse, dans l’ordre inverse. C’est là que se mesure le temps de réponse, que les en-têtes sont apposés et que le logging enregistre ce qui s’est réellement passé. Une couche qui court-circuite ne saute pas seulement le handler ; elle saute les deux moitiés de chaque couche interne — ce qui est précisément la garantie qu’une barrière d’authentification est là pour offrir.
Les bugs d’ordre qui partent en production
Le conseil générique, c’est « l’ordre compte » ; les bugs concrets sont plus instructifs. L’autorisation avant l’authentification : la vérification des permissions s’exécute contre un principal anonyme — des 401/403 intermittents, aucune exception nulle part, des heures de débogage. Le body parser après les routes : chaque handler voit req.body === undefined et accuse le client. L’auth avant CORS : le navigateur bloque la réponse 401 elle-même faute d’en-têtes CORS, si bien que le frontend voit une erreur réseau au lieu de la vraie. Les fichiers statiques avant l’auth : des fichiers privés servis de bon cœur aux utilisateurs non authentifiés. Le gestionnaire d’erreurs pas en dernier : les erreurs levées après sa position dans la pile ne l’atteignent jamais. Chacun de ces bugs passe un smoke test sur le chemin nominal — les bugs d’ordre sont de ceux qui partent en production.
Court-circuiter : quand ne pas appeler next() est le but
Le contrat a deux sorties légales : passer la main, ou terminer le cycle. Le terminer tôt n’est pas un échec du middleware — c’est la moitié de son travail : le 401 de la barrière d’auth, le 429 du limiteur, le hit de cache, la redirection, le preflight CORS traité sur-le-champ. La règle qui garde ces deux sorties honnêtes : faites toujours exactement l’une des deux — répondre, ou next(). Ne faire ni l’un ni l’autre suspend la requête indéfiniment ; faire les deux lève des erreurs headers-already-sent qui déroutent tout le monde en aval.
La même idée dans chaque framework
| Framework | Le middleware est | Passer la main | Au retour |
|---|---|---|---|
| Express | (req, res, next) => {} | next() | Le code après next() (avec précaution) |
| Django | Un callable qui enveloppe get_response | get_response(request) | Le code après l’appel — l’oignon |
| Rack / Rails | Un objet avec call(env) | @app.call(env) | Après le retour de l’appel |
| Koa / Hono | async (ctx, next) => {} | await next() | Après le await — nativement |
Un modèle, quatre accents. Le chemin d’erreur a sa propre convention dans chaque framework — dans Express, la signature à quatre arguments (err, req, res, next) est le mécanisme d’enregistrement, ce qui explique pourquoi supprimer un paramètre « inutilisé » transforme silencieusement le gestionnaire d’erreurs en middleware ordinaire qui ne se déclenche jamais.
Middleware vs. passerelles vs. hooks
| Middleware | Passerelle d’API | Hooks de données | |
|---|---|---|---|
| S’exécute | Dans le processus d’une app | Devant de nombreuses apps | Autour des opérations sur les données |
| Granularité | Par requête | Par requête, entre services | Par save/delete/find |
| Maîtrise | Le pipeline de cette app | Routage, auth en bordure, limites globales | Validation, réactions aux données |
| Configuré par | Du code, dans l’ordre | La configuration de l’infrastructure | Un enregistrement par classe |
Trois couches d’interception, une imbrication : la passerelle se place devant la flotte, le middleware fait passer chaque app par sa batterie de contrôles, et les hooks se déclenchent là où les requêtes deviennent des données. Une préoccupation appartient à la couche la plus externe capable de la trancher — limites de débit globales à la passerelle, auth de session dans le middleware, « cette écriture est-elle valide ? » dans le hook.
Cas d’usage courants
- Authentification et gestion des sessions — établir l’identité une fois, tôt, pour tout ce qui suit.
- Hygiène transversale — CORS, en-têtes de sécurité, compression, ID de requête.
- Discipline des entrées — parsing du body avec limites de taille, contrôle du content-type, validation.
- Observabilité — logging et mesure des temps autour de tout le pipeline, via le chemin de retour de l’oignon.
- Protection du trafic — limites de débit et barrières anti-abus qui court-circuitent avant que le coût ne soit engagé.
Dans quelle couche cela doit-il vivre ? Matrice de décision
| Préoccupation | Couche |
|---|---|
| S’applique à toutes les apps que vous exploitez | Passerelle |
| S’applique à chaque requête de cette app | Middleware, positionné délibérément |
| S’applique à des routes précises | Middleware au niveau du routeur |
| S’applique à l’écriture ou à la lecture des données | Hooks beforeSave / beforeFind |
| Opérations métier sur mesure | Des fonctions, pas des bricolages de pipeline |
| Mise en forme des erreurs | Middleware d’erreurs — en dernier, quatre arguments, sans exception |
Limites et trade-offs
- L’ordre est invisible jusqu’au jour où il ne l’est plus. La pile se lit de haut en bas mais échoue d’une manière qui pointe partout ailleurs ; traitez l’enregistrement des middlewares comme du code critique, soumis à revue.
- Chaque couche taxe chaque requête. Dix middlewares à 2 ms chacun, ce sont 20 ms sur chaque réponse ; mesurez la pile comme vous mesurez les requêtes en base.
- L’état global est un piège. Le middleware s’exécute en concurrence sur plusieurs requêtes ; tout état mutable partagé devient une race condition — attachez les données propres à la requête à l’objet requête, et nulle part ailleurs.
- Les pipelines masquent le flux de contrôle. Une couche qui court-circuite trois niveaux plus bas peut expliquer pourquoi une route « ne s’exécute jamais » ; le bon réflexe de débogage reste toujours le même : afficher la pile, dans l’ordre.
- Tout ne relève pas du pipeline. La logique métier glissée en douce dans un middleware y couple chaque route ; le pipeline sert aux préoccupations transversales, pas aux préoccupations centrales.
Le middleware sur Back4app
Back4app est une plateforme open-source de Backend as a Service (BaaS) qui combine une base de données gérée, des API REST et GraphQL générées automatiquement, l’authentification, le stockage de fichiers et des fonctions serverless avec Cloud Code. La relation est ici d’une littéralité rare : le serveur de Back4app est lui-même un middleware Express — l’onglet JavaScript le montre monté avec app.use('/parse', …) dans une pile standard — et la plateforme exécute la batterie canonique pour chaque requête : en-têtes de sécurité, CORS, parsing borné, vérification des clés, authentification de session et limites de débit, dans le bon ordre, maintenus comme de l’infrastructure. Votre logique personnalisée par requête va alors là où pointe la matrice de décision plutôt que dans du code de pipeline fait main : les triggers beforeSave/beforeFind pour les règles proches des données, les Cloud Functions pour les opérations — chacun avec le contexte utilisateur de la requête attaché, ce qui représente l’essentiel de ce qu’un middleware sur mesure a toujours voulu savoir.
Questions fréquentes
Qu'est-ce qu'un middleware, en termes simples ?
Une fonction placée sur le trajet entre une requête entrante et la logique de votre route, qui traite chaque requête au passage — comme les contrôles de sécurité d'un aéroport avant la porte d'embarquement. Chacune inspecte ou modifie la requête, puis la laisse passer ou l'arrête net.
Quels sont les exemples courants de middleware ?
La pile habituelle : en-têtes de sécurité, CORS, parsing du body avec limites de taille, logging, authentification, autorisation, rate limiting, service des fichiers statiques et — tout à la fin — les handlers 404 et d'erreurs. Presque tout ce qui est transversal dans une application web est un middleware.
Comment fonctionne la chaîne de middlewares ?
Chaque fonction soit termine le cycle en envoyant une réponse, soit appelle next() pour passer la main à la suivante ; le framework parcourt la pile dans l'ordre d'enregistrement jusqu'à ce que quelque chose réponde. Le bug classique : ni répondre ni appeler next() — la requête reste suspendue indéfiniment.
L'ordre des middlewares a-t-il de l'importance ?
C'est la source de bugs numéro un. L'autorisation avant l'authentification vérifie des permissions pour personne ; un body parser placé après les routes laisse req.body à undefined ; l'auth avant CORS pousse les navigateurs à masquer la vraie erreur ; un gestionnaire d'erreurs placé ailleurs qu'en dernier n'attrape rien. L'ordre, c'est le programme.
Qu'est-ce qu'un middleware de gestion des erreurs ?
Un middleware vers lequel le framework achemine les erreurs au lieu de la chaîne normale — dans Express, reconnaissable à sa signature à quatre arguments (err, req, res, next) et enregistré en dernier. Les erreurs levées et les appels next(err) sautent tout le reste pour y atterrir, ce qui explique pourquoi sa position n'est pas négociable.
Quelle est la différence entre un middleware et un handler de route ?
L'intention et la position. Un middleware traite des préoccupations transversales pour de nombreuses routes et passe généralement la main ; le handler de route est la destination qui produit la réponse. Dans la plupart des frameworks, ce sont des fonctions structurellement identiques — le pipeline se termine simplement sur l'une d'elles.
Quelle est la différence entre un middleware et une passerelle d'API ?
La portée. Un middleware s'exécute dans le processus d'une seule application, à chaque requête ; une passerelle est une infrastructure placée devant de nombreuses applications, qui gère routage, auth et limites de débit entre services. Une passerelle est le middleware de toute votre architecture — et les deux se combinent plutôt qu'ils ne s'opposent.
Quand un middleware ne doit-il PAS appeler next() ?
Quand il a entièrement traité la requête : un refus d'auth qui renvoie 401, un rate limiter qui renvoie 429, un hit de cache, une redirection, une réponse à un preflight CORS. Court-circuiter est la fonctionnalité — la garantie que rien au-delà de la barrière ne s'exécute pour les requêtes qui ne l'ont pas franchie.