Express.js is a lightweight, flexible web framework for Node.js, and one of its core features is middleware. Middleware functions make it possible to handle various aspects of HTTP requests and responses, simplifying server logic and making the code modular and reusable.
This guide explores middleware in Express.js: what it is, types of middleware, how to create custom middleware, and some popular use cases.
What is Middleware in Express.js?
Middleware in Express.js refers to functions that have access to the request (req
), response (res
), and the next function in the application’s request-response cycle. Middleware functions can perform various actions, including:
- Executing code.
- Modifying req and res.
- Ending the request-response cycle.
- Passing control to the next middleware function in the stack.
If a middleware function doesn’t end the cycle, it needs to call next()
to transfer control to the following middleware. Omitting next()
will halt the request flow.
Basic Structure of Middleware
A typical middleware function has this structure:
function middlewareFunction(req, res, next) {
// Actions or modifications on req or res
next(); // Pass control to the next middleware
}
Types of Middleware in Express.js
Express includes several categories of middleware:
- Application-Level Middleware
- Router-Level Middleware
- Built-in Middleware
- Error-Handling Middleware
- Third-Party Middleware
Application-Level Middleware
Application-level middleware is used across the app instance and is commonly added using app.use()
or app.METHOD()
, where METHOD represents an HTTP method like get
or post
.
const express = require('express');
const app = express();
app.use((req, res, next) => {
console.log('Timestamp:', Date.now());
next();
});
Router-Level Middleware
Router-level middleware operates similarly to application-level middleware but is applied to an instance of express.Router()
. This is useful for organizing middleware by route.
const router = express.Router();
router.use((req, res, next) => {
console.log('Request URL:', req.originalUrl);
next();
});
app.use('/api', router);
Built-In Middleware
Express provides a few built-in middleware functions:
- express.static: Serves static files, such as CSS, images, and JavaScript.
- express.json: Parses incoming requests with JSON payloads.
- express.urlencoded: Parses URL-encoded data (from forms).
Example of serving static files:
app.use(express.static('public'));
Error-Handling Middleware
Error-handling middleware deals with errors that occur during the request-response lifecycle. It has four parameters: err
, req
, res
, and next
.
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something went wrong!');
});
Third-Party Middleware
Express has a vast ecosystem of third-party middleware. For instance, morgan
is widely used for logging HTTP requests.
const morgan = require('morgan');
app.use(morgan('combined'));
Common Use Cases for Middleware
Logging
Logging middleware tracks details about each incoming request, making debugging easier.
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
Authentication
Authentication middleware verifies users’ identities, such as checking for a valid JSON Web Token (JWT) in the request headers.
const jwt = require('jsonwebtoken');
function authenticateToken(req, res, next) {
const token = req.headers['authorization'];
if (!token) return res.sendStatus(401);
jwt.verify(token, process.env.TOKEN_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
}
app.use(authenticateToken);
Serving Static Files
With express.static
, you can serve static assets like images, CSS, and JavaScript files.
app.use(express.static('public'));
Parsing JSON and Form Data
Use the express.json()
and express.urlencoded()
middleware to handle JSON data and URL-encoded form data.
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
Error Handling
Centralized error-handling middleware allows you to handle errors consistently throughout the application.
app.use((err, req, res, next) => {
console.error(err.message);
res.status(500).send('Server Error');
});
Creating Custom Middleware
Creating custom middleware is straightforward in Express. Here’s an example of middleware that logs the HTTP method and URL for each request:
function requestLogger(req, res, next) {
console.log(`Request Method: ${req.method}, URL: ${req.url}`);
next();
}
app.use(requestLogger);
You can also apply custom middleware to specific routes:
app.get('/user', requestLogger, (req, res) => {
res.send('User Route');
});
Understanding Middleware Execution Order
Middleware functions are executed in the order they’re added to the app with app.use()
or app.METHOD()
. The sequence matters: authentication should come before route handling, logging before processing, etc.
For example, an authentication middleware must come before the protected routes:
app.use(authenticateToken); // Ensure the user is authenticated first
app.get('/protected', (req, res) => {
res.send('Protected Content');
});
Benefits of Middleware in Express.js
- Modularity: Middleware lets you separate different aspects of the application into isolated functions, enhancing code clarity.
- Reusability: Middleware functions are easy to reuse across routes or even different projects.
- Readability: By splitting functionality into distinct middleware, you keep the code cleaner and more maintainable.
Conclusion
Middleware is essential for building efficient and organized applications in Express.js. It gives you fine-grained control over each request’s flow, allowing you to add logging, authentication, error handling, and more. Once you get comfortable with middleware, you can customize the request-response cycle, improving both user experience and development flexibility. Understanding middleware will unlock the full power of Express.js for scalable, efficient server-side applications.