The best examples of middleware in Express.js: 3 practical examples for real apps
Let’s skip the theory and go straight to real code. These three are the best examples of middleware in Express.js that almost every production app uses:
- Request logging
- Authentication / authorization
- Centralized error handling
We’ll start with these 3 practical examples, then branch into more specialized patterns.
Practical example of logging middleware (with request IDs)
A classic example of middleware in Express.js is a logger that runs on every request. In 2024 and beyond, most teams want two things from logging:
- Structured logs (JSON, not random strings)
- A way to trace a single request across services (request IDs)
Here’s a realistic logging middleware using morgan plus a custom request ID using crypto:
const express = require('express');
const morgan = require('morgan');
const crypto = require('crypto');
const app = express();
// 1) Request ID middleware
function requestIdMiddleware(req, res, next) {
const existingId = req.headers['x-request-id'];
const id = existingId || crypto.randomUUID();
req.id = id;
res.setHeader('X-Request-Id', id);
next();
}
// 2) Structured logging middleware using morgan
morgan.token('id', (req) => req.id);
const loggerMiddleware = morgan(
':method :url :status :res[content-length] - :response-time ms :id'
);
app.use(requestIdMiddleware);
app.use(loggerMiddleware);
app.get('/health', (req, res) => {
res.json({ status: 'ok', requestId: req.id });
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
This is one of the best examples of middleware in Express.js because it shows:
- How a custom middleware (
requestIdMiddleware) can enrich thereqandresobjects - How third-party middleware (
morgan) fits into the same pipeline - How multiple middleware functions can stack for every request
In real apps, teams often ship these logs to services like Datadog, New Relic, or open-source stacks like the Elastic Stack. The Node.js documentation itself encourages structured logging for production systems.
Authentication: real examples of middleware in Express.js with JWT
Another classic example of middleware in Express.js is authentication. Instead of sprinkling jwt.verify everywhere, you wrap it in a single reusable function.
Here’s a common pattern using JSON Web Tokens (JWT) and async/await:
const jwt = require('jsonwebtoken');
function authMiddleware(requiredRole) {
return async function (req, res, next) {
try {
const authHeader = req.headers.authorization || '';
const token = authHeader.startsWith('Bearer ')
? authHeader.slice(7)
: null;
if (!token) {
return res.status(401).json({ error: 'Missing token' });
}
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = payload;
if (requiredRole && payload.role !== requiredRole) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
} catch (err) {
// Token invalid, expired, or verification failed
return res.status(401).json({ error: 'Invalid token' });
}
};
}
// Usage examples
app.get('/profile', authMiddleware(), (req, res) => {
res.json({ user: req.user });
});
app.get('/admin', authMiddleware('admin'), (req, res) => {
res.json({ adminData: true });
});
These routes show real examples of middleware in Express.js:
/profileusesauthMiddleware()with no role restriction/adminusesauthMiddleware('admin')to enforce a specific role
The middleware is reusable, testable, and easy to reason about. You can also plug this into route groups using express.Router() so all admin routes share the same authentication layer.
For background on why token-based auth is still widely used in 2024–2025, the NIST Digital Identity Guidelines discuss modern identity practices and risk-based access control.
Centralized error handling: the cleanup crew
If you’re using async/await, you need a consistent way to catch and format errors. Express has a specific pattern for this: an error-handling middleware with four parameters: (err, req, res, next).
Here’s a practical example of middleware in Express.js that centralizes error handling:
// Async wrapper so you don't repeat try/catch
function asyncHandler(fn) {
return function (req, res, next) {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
// Example route using asyncHandler
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await UserModel.findById(req.params.id);
if (!user) {
const error = new Error('User not found');
error.status = 404;
throw error;
}
res.json(user);
}));
// Error-handling middleware (must come last)
app.use((err, req, res, next) => {
console.error(`[${req.id || 'no-id'}]`, err);
const status = err.status || 500;
const response = {
error: err.message || 'Internal Server Error',
};
if (process.env.NODE_ENV !== 'production') {
response.stack = err.stack;
}
res.status(status).json(response);
});
This is one of the best examples of middleware in Express.js because it:
- Works with async/await without littering routes with try/catch
- Gives you a single place to control error format
- Plays nicely with logging middleware (you can reuse
req.id)
The pattern is still common in 2024–2025, especially in APIs that need consistent JSON error responses for frontend clients or mobile apps.
More real examples of middleware in Express.js you’ll see in production
Those 3 practical examples are the core. But real-world apps usually stack several more middleware functions. Here are additional examples of middleware in Express.js that are common in modern Node stacks.
Security headers with helmet
Security middleware is a low-effort, high-impact win. The helmet package sets common HTTP security headers that help mitigate attacks like XSS and clickjacking.
const helmet = require('helmet');
app.use(helmet());
You can customize it too:
app.use(helmet({
contentSecurityPolicy: false, // turn off if you manage CSP separately
}));
The OWASP Foundation maintains guidance on secure headers that informs libraries like helmet. This is a simple example of middleware in Express.js that pays off immediately.
Rate limiting to protect APIs
With more public APIs and AI-driven scraping in 2024–2025, rate limiting is no longer optional for most public endpoints. A popular choice is express-rate-limit:
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per window
standardHeaders: true,
legacyHeaders: false,
});
// Apply to all /api routes
app.use('/api', apiLimiter);
This is another clear example of middleware in Express.js: a function that inspects the request (IP, route), applies some logic, and either calls next() or blocks the request.
JSON body parsing and input validation
Body parsing used to be handled by body-parser, but Express now ships with express.json() and express.urlencoded() built in.
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
On top of that, many teams add validation middleware, often using libraries like joi or zod:
const Joi = require('joi');
function validateBody(schema) {
return (req, res, next) => {
const { error, value } = schema.validate(req.body, {
abortEarly: false,
stripUnknown: true,
});
if (error) {
return res.status(400).json({
error: 'Validation failed',
details: error.details.map((d) => d.message),
});
}
req.body = value;
next();
};
}
const createUserSchema = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(8).required(),
});
app.post('/users', validateBody(createUserSchema), asyncHandler(async (req, res) => {
const user = await UserModel.create(req.body);
res.status(201).json(user);
}));
Here you see another real example of middleware in Express.js: a reusable validator that runs before the route handler and either short-circuits with a 400 or normalizes the input.
For general guidance on input validation and security, the National Institute of Standards and Technology (NIST) provides security publications that many organizations reference when designing secure APIs.
Request timing and performance metrics
In 2024–2025, observability is a first-class concern. A lightweight custom middleware can record request duration and send it to a metrics system.
function timingMiddleware(req, res, next) {
const start = process.hrtime.bigint();
res.on('finish', () => {
const end = process.hrtime.bigint();
const durationMs = Number(end - start) / 1_000_000;
console.log(JSON.stringify({
path: req.path,
method: req.method,
status: res.statusCode,
durationMs,
}));
// In a real app, send to Prometheus, StatsD, etc.
});
next();
}
app.use(timingMiddleware);
This pattern is an example of middleware in Express.js that hooks into the response lifecycle (res.on('finish')) to collect metrics without changing individual route handlers.
CORS middleware for frontend–backend integration
If you’re serving a React, Vue, or Next.js frontend from a different origin, you need CORS. The cors package wraps that logic in a clean middleware.
const cors = require('cors');
const corsOptions = {
origin: ['https://example.com', 'https://admin.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true,
};
app.use(cors(corsOptions));
This is one of those quiet but important examples of middleware in Express.js that makes cross-origin requests behave without you thinking about headers on every route.
Putting it all together: composing multiple middleware functions
The real power of Express comes when you combine these patterns. A single route can use a chain of middleware to enforce security, validation, and business logic in a predictable order.
Here’s a realistic route that composes several examples of middleware in Express.js:
app.post(
'/posts',
cors(), // allow cross-origin if needed
authMiddleware(), // ensure user is authenticated
validateBody(postSchema), // validate input
rateLimit({ // per-route rate limit
windowMs: 60 * 1000,
max: 10,
}),
asyncHandler(async (req, res) => {
const post = await PostModel.create({
...req.body,
userId: req.user.id,
});
res.status(201).json(post);
})
);
Each line is an example of middleware in Express.js doing one job:
- CORS
- Auth
- Validation
- Rate limiting
- Async route logic with centralized error handling
This style keeps your code readable and makes it easy to swap in better implementations over time.
FAQ: Express.js middleware, with real examples
What are some common examples of middleware in Express.js?
Common examples include logging (like morgan), authentication middleware using JWT, error-handling middleware, security headers with helmet, rate limiting with express-rate-limit, body parsing with express.json(), validation middleware using joi or zod, and CORS middleware using the cors package.
Can you give an example of custom middleware in Express.js?
Yes. A simple example of custom middleware is a function that adds a timestamp to each request:
function timestampMiddleware(req, res, next) {
req.requestTime = new Date().toISOString();
next();
}
app.use(timestampMiddleware);
app.get('/time', (req, res) => {
res.json({ now: req.requestTime });
});
This example of middleware shows how you can enrich the req object and use that data later in your routes.
How do error-handling middleware examples in Express.js differ from normal middleware?
Error-handling middleware has four parameters: (err, req, res, next). Express uses that signature to recognize it as an error handler. Normal middleware has only three parameters: (req, res, next). You typically put error-handling middleware at the end of your middleware stack so it can catch errors from any previous handler.
Are third-party packages the only good examples of middleware in Express.js?
Not at all. Third-party packages like helmet, cors, and express-rate-limit are strong examples of middleware in Express.js, but many of the most valuable patterns in your app will be custom: your own auth logic, your own validation rules, your own logging format, and your own business-specific checks.
Where can I learn more about Express.js middleware patterns?
The official Express.js guide is still the best starting point. For broader Node and web security practices that influence many of these examples, the OWASP Cheat Sheet Series and NIST publications are widely referenced by engineering teams.
Related Topics
Real examples of deploying Node.js on Heroku: 3 practical examples
Practical examples of basic HTTP server examples in Node.js
3 Best Examples of Creating a RESTful API with Express.js
Examples of Multer File Uploads in Node.js: 3 Practical Patterns You’ll Actually Use
Modern examples of handling errors in Node.js applications
Modern examples of command-line application examples in Node.js
Explore More Node.js Code Snippets
Discover more examples and insights in this category.
View All Node.js Code Snippets