Middleware Patterns in Node.js
Middleware is the backbone of Express.js. It’s the mechanism that lets you inject logic into the request-response cycle, making your application flexible and modular. This tutorial covers the fundamental middleware patterns you need to build robust Node.js applications.
What Is Middleware?
Middleware functions are functions that have access to three things: the request object (req), the response object (res), and the next function in the application’s request-response cycle.
A middleware function can:
- Execute any code
- Make changes to the request or response objects
- End the request-response cycle
- Call the next middleware in the stack
If a middleware doesn’t end the cycle, it must call next() to pass control to the next middleware. Forgetting to call next() is a common mistake that leaves requests hanging.
The Basic Middleware Signature
Every middleware function follows this signature:
function middlewareName(req, res, next) {
// Your logic here
next(); // Pass control to the next middleware
}
The three parameters are conventionally named req, res, and next. You can name them anything, but sticking to the convention makes your code readable to other developers.
Creating Your First Middleware
Here’s a simple logging middleware that logs when a request hits your server:
const myLogger = function (req, res, next) {
console.log('Request received:', req.method, req.url);
next();
};
app.use(myLogger);
The order matters. If you load this middleware after a route, the request never reaches it because the route handler terminates the cycle first.
Middleware That Modifies the Request
You can add properties to the request object that downstream handlers can use:
const requestTime = function (req, res, next) {
req.requestTime = Date.now();
next();
};
app.use(requestTime);
app.get('/', (req, res) => {
res.send(`Page rendered at: ${new Date(req.requestTime)}`);
});
This pattern is useful for adding timestamps, user information from authentication, or any data computed early in the cycle.
Configurable Middleware
Create reusable middleware by exporting a function that accepts options:
// my-middleware.js
module.exports = function (options) {
return function (req, res, next) {
// Use options.foo, options.bar here
if (options.required && !req.headers[options.required]) {
return res.status(400).send('Missing required header');
}
next();
};
};
// Usage
const mw = require('./my-middleware');
app.use(mw({ required: 'x-api-key' }));
This pattern is how popular middleware like cors and helmet work — they accept configuration and return a middleware function.
Error Handling Middleware
Error handling middleware is special. It takes four parameters instead of three:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something went wrong!');
});
Express identifies error handlers by their four-parameter signature. When you call next(err), Express skips all regular middleware and routes, going straight to your error handler.
Async Middleware
Async middleware requires special handling. If your middleware returns a Promise, Express 5 will automatically call next() with the rejection reason:
// Express 5 (automatic)
async function validateCookies(req, res, next) {
await checkCookies(req.cookies);
next();
}
// Express 4 (manual error handling)
function validateCookies(req, res, next) {
checkCookies(req.cookies)
.then(() => next())
.catch(next); // Pass error to Express
}
In Express 4, always catch async errors and pass them to next(). Otherwise, unhandled rejections crash your server.
Chaining Middleware
The power of middleware comes from chaining. Each middleware can:
- Perform its task and call
next() - Send a response and end the cycle
- Pass an error to
next(err)
app.use(checkAuth); // 1. Verify user is authenticated
app.use(loadUser); // 2. Load user data into req.user
app.use(logRequest); // 3. Log the request
app.get('/profile', (req, res) => { // 4. Handle the route
res.json(req.user);
});
This chain pattern is how authentication, logging, parsing, and routing all work together cleanly.
Third-Party Middleware
Express includes minimal built-in middleware. For common tasks, use proven third-party packages:
| Package | Purpose |
|---|---|
morgan | HTTP request logging |
helmet | Security headers |
cors | Cross-origin resource sharing |
cookie-parser | Parse cookies |
body-parser | Parse request bodies |
const helmet = require('helmet');
const morgan = require('morgan');
app.use(helmet());
app.use(morgan('tiny'));
See Also
- Building a Web App with Express — Full Express application setup
- Designing a REST API with Node.js — Building APIs with proper middleware
- Modules and npm — Understanding Node.js module system