Posted by Kosal
Express.js, the minimalist web framework for Node.js, owes much of its flexibility and power to middleware. Middleware functions are the backbone of Express apps—they can process requests, modify responses, and control application flow.
If you're building scalable and maintainable applications, understanding and using middleware effectively is essential. In this article, we'll explore what middleware is, how it works, and best practices for using it in your Express apps.
In Express, middleware is a function that has access to the request (req
), response (res
), and the next function in the application’s request-response cycle. Middleware functions can:
next()
middleware in the stackfunction middlewareExample(req, res, next) {
// Do something with req or res
next(); // Call next middleware
}
You use it in an Express app like this:
app.use(middlewareExample);
Application-Level Middleware
Bound to an instance of express()
using app.use()
or app.METHOD()
.
Router-Level Middleware
Similar to application-level but bound to an instance of express.Router()
.
Error-Handling Middleware
Has four arguments: (err, req, res, next)
. Used for catching and handling errors.
Built-in Middleware
Express provides built-in middleware such as express.json()
and express.static()
.
Third-Party Middleware
Middleware provided by the community, like morgan
, cors
, body-parser
, etc.
When a request is made to the Express server, it flows through the middleware stack in the order they are defined:
app.use(middleware1);
app.use(middleware2);
app.get('/', (req, res) => {
res.send('Hello World!');
});
If middleware1
calls next()
, then middleware2
is executed. Otherwise, the response ends there.
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
app.use(express.json());
function authenticate(req, res, next) {
if (req.headers.authorization === 'Bearer valid-token') {
next();
} else {
res.status(401).send('Unauthorized');
}
}
app.use('/api', authenticate);
To handle errors gracefully:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something went wrong!');
});
Ensure it's defined after all other app.use()
and route calls.
Keep Middleware Single-Purpose
Each middleware should do one thing—log, authenticate, validate, etc.
Use Router-Level Middleware
Attach middleware to routers to keep concerns isolated.
const userRouter = express.Router();
userRouter.use(authenticate);
userRouter.get('/profile', (req, res) => { ... });
app.use('/users', userRouter);
Avoid Side Effects
Middleware should not unintentionally modify shared objects unless necessary.
Use Third-Party Middleware When Appropriate
Libraries like helmet
(security), cors
, and morgan
save time and improve reliability.
Order Matters
Define middleware in the correct order—logging, parsing, authentication, routing, then error handling.
Middleware is one of the most powerful and flexible features in Express.js. Whether you're logging requests, securing routes, or catching errors, middleware allows you to build modular and maintainable applications.
By following best practices and structuring your middleware wisely, you can keep your Express apps clean, efficient, and scalable.