Best examples of adding middleware in a Node.js API – Practical Examples for 2025
Real-world examples of adding middleware in a Node.js API – practical examples
Let’s start where most developers actually start: taking a plain Express server and layering in middleware until it feels like a real API instead of a weekend experiment.
Here’s a minimal Express setup we’ll keep extending:
const express = require('express');
const app = express();
app.use(express.json()); // built-in middleware
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: Date.now() });
});
app.listen(3000, () => {
console.log('API listening on http://localhost:3000');
});
From here, we’ll walk through several examples of adding middleware in a Node.js API – practical examples that mirror what you’d see in a production backend.
Logging and request tracing – the first middleware you actually feel
If you’re looking for a simple example of adding middleware in a Node.js API, request logging is the classic starting point. You want to know who hit what endpoint, when, and with which status code.
A lightweight, no-dependency logger middleware might look like this:
function requestLogger(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(
`\({req.method} }\(req.originalUrl} \({res.statusCode} - }\(duration}ms`
);
});
next();
}
app.use(requestLogger);
Real examples in production often pair this with a correlation ID so you can trace a single request across services.
const { randomUUID } = require('crypto');
function correlationId(req, res, next) {
const id = req.headers['x-correlation-id'] || randomUUID();
req.correlationId = id;
res.setHeader('X-Correlation-Id', id);
next();
}
app.use(correlationId);
These two together are among the best examples of middleware that pay off instantly: debugging, performance tuning, and observability all become easier.
Security and headers – examples include helmet, CORS, and rate limiting
Modern APIs are expected to ship with sane security defaults. Middleware is where you put them.
Security headers with helmet
One of the most common examples of adding middleware in a Node.js API – practical examples you’ll see on GitHub – is using helmet:
const helmet = require('helmet');
app.use(helmet());
This adds HTTP headers that help protect against common attacks like XSS or clickjacking. While you should always pair this with secure coding practices and testing, it’s a low-friction upgrade.
For security guidance and why these headers matter, the OWASP organization maintains a useful overview of HTTP security controls: https://owasp.org/www-project-secure-headers/.
CORS middleware
If your frontend is on a different domain than your API, you need Cross-Origin Resource Sharing (CORS) configured.
const cors = require('cors');
const corsOptions = {
origin: ['https://app.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true
};
app.use(cors(corsOptions));
This is another everyday example of adding middleware in a Node.js API: you centralize cross-origin logic instead of sprinkling headers across routes.
Rate limiting
To keep bots or misbehaving clients from hammering your endpoints, you add a rate limiter. A common middleware pattern uses 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, // RateLimit-* headers
legacyHeaders: false
});
app.use('/api', apiLimiter);
Even in 2025, this remains one of the best examples of a simple mitigation against basic abuse. For more on API security best practices, the NIST publications library is a solid reference point, especially when you’re operating in regulated environments.
Authentication and authorization – JWT middleware in action
Authentication middleware is where “toy API” turns into “real application.” Here’s a straightforward JWT auth example of adding middleware in a Node.js API.
const jwt = require('jsonwebtoken');
function authMiddleware(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Missing token' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid token' });
}
req.user = user; // attach decoded payload
next();
});
}
app.get('/profile', authMiddleware, (req, res) => {
res.json({ user: req.user });
});
Here, authMiddleware is a textbook example of route-level middleware: you attach it only where needed instead of globally.
You can layer authorization on top of this with a role-checking middleware:
function requireRole(role) {
return function (req, res, next) {
if (!req.user || req.user.role !== role) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
app.delete('/admin/users/:id', authMiddleware, requireRole('admin'), (req, res) => {
// delete user logic
res.status(204).send();
});
These two together are real examples of adding middleware in a Node.js API that map directly to production-grade security requirements.
Validation middleware – keeping garbage out of your database
By 2025, most serious Node APIs use some form of schema-based validation. Middleware is where you enforce those schemas.
Here’s an example using zod, but the pattern is similar for Joi, Yup, or built-in framework validators:
const { z } = require('zod');
const createUserSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string().min(1)
});
function validateBody(schema) {
return function (req, res, next) {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: 'Validation failed',
details: result.error.issues
});
}
req.body = result.data; // sanitized
next();
};
}
app.post('/users', validateBody(createUserSchema), (req, res) => {
// safe to use req.body here
res.status(201).json({ user: req.body });
});
This is one of the best examples of adding middleware in a Node.js API because it centralizes validation logic and keeps route handlers focused on business rules, not input checking.
For broader data validation concepts and error handling patterns, universities like MIT publish coursework and open materials that are helpful when you want to go deeper on software design and reliability.
Error-handling middleware – the safety net
In Express-style frameworks, error-handling middleware has a distinct signature with four arguments. It’s your last line of defense before an exception becomes a 500 stack trace in someone’s browser.
Here’s a simple pattern:
function errorHandler(err, req, res, next) {
console.error('Error:', err);
const status = err.status || 500;
const message =
status === 500 ? 'Internal server error' : err.message || 'Error';
res.status(status).json({ error: message });
}
app.use(errorHandler);
You can pair this with a small helper that throws HTTP-style errors from your route handlers:
class HttpError extends Error {
constructor(status, message) {
super(message);
this.status = status;
}
}
app.get('/items/:id', async (req, res, next) => {
try {
const item = await findItemById(req.params.id);
if (!item) throw new HttpError(404, 'Item not found');
res.json(item);
} catch (err) {
next(err); // forwarded to errorHandler
}
});
This pattern is one of the cleanest examples of adding middleware in a Node.js API to standardize how your API talks to clients about failures.
Performance and caching middleware – getting faster without rewriting everything
Not every performance problem needs a full rewrite. Sometimes you just need smart middleware.
Simple in-memory cache
Here’s a basic cache middleware for endpoints where data doesn’t change constantly:
const cache = new Map();
function cacheMiddleware(ttlMs) {
return function (req, res, next) {
const key = req.originalUrl;
const cached = cache.get(key);
if (cached && cached.expires > Date.now()) {
return res.json(cached.data);
}
const originalJson = res.json.bind(res);
res.json = (body) => {
cache.set(key, {
data: body,
expires: Date.now() + ttlMs
});
return originalJson(body);
};
next();
};
}
app.get('/public/stats', cacheMiddleware(60 * 1000), async (req, res) => {
const stats = await computeExpensiveStats();
res.json(stats);
});
This is a practical example of adding middleware in a Node.js API to improve perceived speed without touching the core logic.
Response compression
Another low-effort win: gzip or Brotli compression using compression middleware.
const compression = require('compression');
app.use(compression());
You don’t need to micromanage it; the middleware negotiates with the client based on Accept-Encoding headers.
Organizing middleware in 2025: trends and patterns
The Node ecosystem in 2024–2025 has matured past the “everything in one file” era. The best examples of production APIs tend to share a few patterns:
Grouped middleware stacks
Instead of a single giant app.use section, teams group middleware by responsibility:
// security.js
module.exports = function applySecurity(app) {
app.use(helmet());
app.use(cors(corsOptions));
app.use(apiLimiter);
};
// logging.js
module.exports = function applyLogging(app) {
app.use(correlationId);
app.use(requestLogger);
};
// app.js
applyLogging(app);
applySecurity(app);
app.use(express.json());
app.use('/api', apiRouter);
app.use(errorHandler);
This doesn’t change how middleware works, but it’s a real example of adding middleware in a Node.js API in a way that scales as your project grows.
Framework-agnostic thinking
Even if you’re using NestJS, Fastify, or Hapi, the same concepts apply:
- A function runs before or after your handler.
- It can read/modify the request.
- It can short-circuit the response.
Once you understand these examples of adding middleware in a Node.js API with Express, translating them to decorators in NestJS or hooks in Fastify is mostly syntax.
FAQ: common questions about middleware in Node.js APIs
What are some real examples of middleware in a Node.js API?
Real-world examples include:
- Request logging and correlation ID middleware for debugging
- Authentication and authorization middleware using JWTs or sessions
- Security middleware like
helmet, CORS configuration, and rate limiting - Validation middleware that checks request bodies against schemas
- Error-handling middleware that converts exceptions into clean JSON responses
- Performance middleware such as caching and response compression
All of the code snippets above are practical examples of adding middleware in a Node.js API that you can adapt directly.
Can I apply middleware only to specific routes?
Yes. In Express-style frameworks you can attach middleware at the route level:
app.get('/private', authMiddleware, (req, res) => {
res.json({ secret: '42' });
});
Or at the router level:
const adminRouter = express.Router();
adminRouter.use(authMiddleware, requireRole('admin'));
adminRouter.get('/dashboard', (req, res) => {
res.json({ stats: 'admin stuff' });
});
app.use('/admin', adminRouter);
These patterns are some of the best examples of keeping your middleware usage organized and predictable.
How many middleware functions is too many?
There’s no hard number, but you’ll feel it when every request passes through a long chain of logic that’s hard to reason about. A reasonable rule of thumb in 2025 backend teams is:
- Keep global middleware focused on cross-cutting concerns (logging, security, JSON parsing).
- Use route-level middleware for things that are context-specific (auth, validation, feature flags).
If you find yourself stacking five or six middlewares on every route, consider grouping them into a single higher-level function.
Are these examples of middleware compatible with serverless (AWS Lambda, etc.)?
Mostly yes, with some adaptation. Libraries like serverless-http let you wrap an Express app and run it in Lambda, Azure Functions, or similar platforms. Your existing middleware (logging, auth, validation, error handling) generally works without major changes. The main caveats are around connection reuse, cold starts, and some request/response object differences.
Where can I learn more about secure API design beyond middleware?
Middleware is only one layer of API security. For deeper background on authentication, encryption, and secure software practices, look at:
- NIST’s cybersecurity publications: https://csrc.nist.gov/publications
- OWASP’s API Security Top 10: https://owasp.org/www-project-api-security/
- University security courses and open materials, such as those linked from https://web.mit.edu/
These resources go far beyond examples of adding middleware in a Node.js API, but they give you the broader context for why those middleware patterns matter.
Related Topics
Best examples of adding middleware in a Node.js API – Practical Examples for 2025
Real-world examples of 3 examples of setting up a Node.js server
3 Best Examples of Deploying a Node.js API to Heroku
Real-world examples of CORS in Node.js API: 3 practical setups that actually work
Explore More Building a Simple API with Node.js
Discover more examples and insights in this category.
View All Building a Simple API with Node.js