Middleware patterns vormen het hart van elke moderne Node.js backend. Of je nu werkt met Express, Koa of Fastify, middleware is het mechanisme waarmee je een request afhandelt, aanvult en doorstuurt naar de volgende stap in de keten. Wie middleware goed begrijpt, schrijft schonere, beter onderhoudbare en veiligere applicaties.
In deze gids leer je hoe middleware werkt, welke patterns er zijn en hoe je ze in de praktijk toepast. We kijken naar authenticatie, logging, error handling en composable middleware, met concrete codevoorbeelden die je direct kunt gebruiken.
Wat is middleware precies?
Middleware is een functie die zich tussen de inkomende request en het uiteindelijke response bevindt. In Express heeft elke middleware drie parameters: req, res en next. De eerste twee ken je al uit de basis van HTTP servers en routing. De derde, next, is een functie die de controle doorgeeft aan de volgende middleware.
function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next();
}
Het mooie is dat je zoveel middleware-functies kunt stapelen als je wilt. Ze worden in volgorde uitgevoerd, en elke functie mag beslissen of ze het request doorgeeft, aanvult of afbreekt.
De request-response cyclus
Elke request doorloopt een pijplijn. De middleware-keten wordt van boven naar beneden uitgevoerd, totdat ergens een response wordt verstuurd of next(err) een fout signaleert. Dit noem je ook wel het chain-of-responsibility pattern.
Stel je een request voor die binnenkomt op POST /api/users. Eerst gaat deze door een logger, dan door een body parser, vervolgens door authenticatie, daarna door validatie, en tot slot bereikt hij de route handler die de gebruiker aanmaakt.
Types middleware in Express
Express onderscheidt verschillende niveaus waarop je middleware kunt registreren. Elk niveau heeft zijn eigen toepassing.
Application-level middleware
Deze middleware geldt voor de hele applicatie. Je registreert het met app.use() en het wordt uitgevoerd voor elke inkomende request, ongeacht de route.
const express = require('express');
const app = express();
app.use(express.json());
app.use((req, res, next) => {
req.requestTime = Date.now();
next();
});
Route-level middleware
Route-level middleware wordt alleen uitgevoerd voor specifieke routes. Dit is ideaal voor endpoint-specifieke logica, zoals extra validatie of caching.
app.get('/admin', requireAuth, requireAdmin, (req, res) => {
res.send('Admin dashboard');
});
Router-level middleware
Als je modules hebt georganiseerd volgens de principes uit modules en project structuur, werk je waarschijnlijk met express.Router(). Ook hier kun je middleware aan koppelen, zodat een set routes gedeelde logica deelt.
const router = express.Router();
router.use(requireAuth);
router.get('/profile', getProfile);
router.post('/profile', updateProfile);
Error-handling middleware
Error-handlers zijn speciaal: ze hebben vier parameters. Express herkent die signatuur en roept ze alleen aan bij een fout.
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Er ging iets mis' });
});
Praktische middleware patterns
Nu de basis staat, kijken we naar patterns die je in bijna elk project tegenkomt. Deze patronen helpen je om herhaling te vermijden en zorgen voor een consistente architectuur.
1. Logging middleware
Een logger is vaak het eerste wat je bouwt. Gebruik een library zoals morgan voor request-logging, of schrijf een eigen variant als je specifieke velden wilt vastleggen.
function requestLogger(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} ${res.statusCode} ${duration}ms`);
});
next();
}
app.use(requestLogger);
Merk op dat we next() direct aanroepen, maar toch kunnen meten hoelang de request duurde. Dat kan dankzij het finish-event op het response-object.
2. Authenticatie middleware
Een klassieker: controleren of een gebruiker ingelogd is. We verwachten een JWT in de Authorization-header en zetten de geverifieerde user op het request-object.
const jwt = require('jsonwebtoken');
function requireAuth(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) return res.status(401).json({ error: 'Niet geautoriseerd' });
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
res.status(401).json({ error: 'Ongeldig token' });
}
}
Door req.user in te stellen, hebben alle volgende middleware en route handlers toegang tot de ingelogde gebruiker. Dit is het decorate-pattern in actie: de request wordt onderweg verrijkt.
3. Validatie middleware
Data validatie hoort vóór je business logic. Combineer middleware met een library als Zod voor type-safe validatie.
function validate(schema) {
return (req, res, next) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.issues });
}
req.body = result.data;
next();
};
}
app.post('/users', validate(userSchema), createUser);
Dit is een middleware factory: een functie die middleware retourneert. Zo kun je dezelfde logica met verschillende configuraties hergebruiken.
4. Async middleware wrapper
Async programming met promises en async/await is in middleware net zo belangrijk als elders. Express 4 vangt echter geen promise rejections automatisch af. Een wrapper lost dit op.
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await db.users.findById(req.params.id);
if (!user) throw new Error('Niet gevonden');
res.json(user);
}));
In Express 5 is dit overigens niet meer nodig; die vangt async errors standaard op.
5. Rate limiting
Bescherm je API tegen misbruik met rate limiting. De library express-rate-limit is een solide keuze.
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Te veel requests, probeer het later opnieuw'
});
app.use('/api/', apiLimiter);
Volgorde is alles
De volgorde waarin je middleware registreert bepaalt hoe requests worden afgehandeld. Een veelgemaakte fout is dat authenticatie-middleware ná een route wordt geplaatst, waardoor die route onbeschermd blijft.
Een goede volgorde ziet er zo uit:
- Security headers (bijvoorbeeld Helmet)
- CORS
- Body parsers (
express.json(),express.urlencoded()) - Request logging
- Rate limiting
- Authenticatie
- Route handlers
- 404-handler
- Error-handler (altijd als laatste)
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use(requestLogger);
app.use('/api/', apiLimiter);
app.use('/api/', requireAuth);
app.use('/api/users', userRoutes);
app.use((req, res) => res.status(404).json({ error: 'Niet gevonden' }));
app.use(errorHandler);
Middleware composition
Soms wil je meerdere middleware-functies bundelen tot één logische eenheid. Express accepteert zowel losse functies als arrays.
const adminOnly = [requireAuth, requireRole('admin')];
app.get('/admin/users', adminOnly, listUsers);
app.delete('/admin/users/:id', adminOnly, deleteUser);
Dit houdt je routes DRY en maakt het direct duidelijk welke bescherming een endpoint heeft.
Error handling goed doen
Een robuuste error-handling middleware is onmisbaar. Je wilt errors loggen, gevoelige informatie niet lekken, en een consistente response-structuur bieden, zoals beschreven in REST API design best practices.
function errorHandler(err, req, res, next) {
const status = err.statusCode || 500;
const message = status === 500 ? 'Interne serverfout' : err.message;
if (status === 500) {
console.error(err);
}
res.status(status).json({
error: message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
}
Gooi in je route handlers custom errors met een statusCode, en laat deze centrale handler ze netjes vertalen naar een HTTP-response.
Middleware buiten Express
Het middleware pattern is krachtig genoeg om buiten Express toe te passen. Koa gebruikt een async/await-gebaseerde variant met ctx en een next-functie die een promise retourneert. Fastify kent hooks die vergelijkbaar werken. Ook als je rechtstreeks met de http-module werkt (zie je eerste Node.js server bouwen), kun je zelf een simpele middleware-runner schrijven.
function compose(middlewares) {
return (req, res) => {
let i = 0;
const next = () => {
const fn = middlewares[i++];
if (fn) fn(req, res, next);
};
next();
};
}
Dit laat zien dat middleware niet magisch is: het is gewoon een elegant pattern voor het samenstellen van functies.
Best practices op een rij
- Houd middleware klein en focused op één taak
- Gebruik factory functions voor configureerbare middleware
- Vergeet
next()niet, tenzij je bewust een response verstuurt - Registreer error-handlers altijd als laatste
- Log errors, maar lek geen stack traces naar de client in productie
- Test middleware in isolatie met mocks voor
req,resennext - Plaats authenticatie vóór routes die bescherming nodig hebben
- Gebruik bestaande libraries voor standaardtaken zoals CORS en rate limiting
Veelgestelde vragen
Wat is middleware in Node.js?
Middleware is een functie die toegang heeft tot het request- en response-object en de volgende middleware in de keten kan aanroepen. Het is het mechanisme waarmee je functionaliteit zoals logging, authenticatie of validatie kunt toevoegen aan elke inkomende request.
Wat is het verschil tussen application-level en route-level middleware?
Application-level middleware wordt uitgevoerd voor alle routes in je applicatie via app.use(). Route-level middleware geldt alleen voor specifieke routes of methodes en wordt direct aan een route gekoppeld.
Hoe werkt error-handling middleware in Express?
Error-handling middleware heeft vier parameters: err, req, res en next. Express herkent deze signatuur en roept deze middleware alleen aan wanneer er een fout via next(err) wordt doorgegeven of gegooid wordt in een synchrone handler.
Moet ik altijd next() aanroepen in middleware?
Nee, niet altijd. Als je middleware een response verstuurt met res.send() of res.json() hoef je next() niet aan te roepen. Roep je geen van beide aan, dan blijft de request hangen totdat hij timeout.
Kan ik middleware gebruiken buiten Express?
Ja, het middleware pattern is niet gebonden aan Express. Frameworks zoals Koa, Fastify en Hapi hebben eigen middleware-implementaties, en je kunt het patroon zelfs zelf bouwen bovenop de native http-module.