Introduction
If you have ever worked with Express.js, you have probably heard the term middleware. Middleware is simply a function that executes between receiving a request and sending a response.
These functions have access to the req object, the res object, and the next function, which is used to pass control to the next middleware in the chain.
Express.js also provides built-in middleware, meaning we do not need to install additional dependencies or packages to use them. At the same time, Express gives developers the flexibility to create custom middleware because every application has its own business logic and unique use cases.
So, let’s get started and understand what middleware actually is, how its flow works, and the different types of middleware available in Express.js.
Middlewares in Express
Express.js is a JavaScript web framework used for building Node.js applications. It is a very robust framework because it provides many built-in features along with the flexibility to create custom functionality.
Middleware is one of those powerful features. We have already gotten a high-level overview of middleware, so now let’s understand it with a simple real-world analogy.
Middleware in Express.js can be compared to airport security checks.
Before passengers reach their gate, they go through multiple verification steps such as ticket checking and security screening.
Similarly, in Express.js, a request passes through multiple middleware functions before reaching the final route handler. Each middleware can process the request, modify it, block it, or pass it to the next middleware using
next().
Where Middleware Sits in Request Lifecycle?
Middleware functions are placed at an intermediate level in the request-response cycle. When a request enters an Express.js application, it first encounters the first middleware function. Based on the requirement, the middleware can modify the req or res object and then call the next() function.
The next() function passes control to the next middleware function in the chain. Any changes made to the req or res object persist throughout the remaining middleware functions and route handlers.
If a middleware neither calls next() nor sends a response, the request-response cycle will stop, causing the request to hang until it eventually times out. Therefore, every middleware should either pass control using next() or terminate the cycle by sending a response.
Types of Middleware
Generally there are multiple types of middleware in express but in this blog we will talk about
Application-level middleware
Router-level middleware
Built-in middleware
Application-level Middleware
These are bound to an instance of the app object They execute for every request received by the application if no specific path is provided, or for specific routes if defined.
app.use()app.get()app.METHOD()
Common uses: Global logging, authentication checks, and setting custom headers
Router-level Middleware
This works exactly like application-level middleware but is bound to an instance of express.Router(). This allows you to modularize your code by grouping related routes and applying middleware only to that specific group.
- Common uses: Protecting a specific set of "admin" or "user" routes
Built-in Middleware
Starting with version 4.x, Express moved most of its features into separate modules, but it still maintains a few core built-in functions
express.json(): Parses incoming requests with JSON payloads.
express.urlencoded(): Parses incoming requests with URL-encoded payloads (form data).
express.static(): Serves static assets like HTML files, CSS, and image.
Role of next() Function
A middleware function has three parameters: the req object, the res object, and the next() function. The next() function is very important because, after completing the middleware process, we need to call it to pass control to the next middleware function or route handler.
If next() is not called inside a middleware, Express.js will assume that the middleware has ended the request-response cycle. In that case, if no response is returned using methods like res.send() or res.json(), the request will remain stuck in that middleware until it eventually times out.
When we call next(), Express moves the flow to the next middleware or route handler. Any changes made to the req or res object inside one middleware will also persist in the next middleware functions.
Real-World Examples
As we discussed in express we have buit-in middleware but we can also create customer middleware.
in this section will see the usecase of middleware in actual application flow. middleware in express server can be used for:
Logging
Logging middleware in Express.js is used to record details about incoming HTTP requests and outgoing responses, which is essential for debugging, monitoring, and auditing application health. You can implement logging using built-in custom functions or third-party libraries like Morgan.
function logger(req, res, next) {
console.log(`\({req.method} \){req.url}`);
next();
}
app.use(logger);
Authentication
Authentication middleware is used to protect routes and verify whether a user is authenticated or not. It checks if the user has valid credentials, such as a session, token, or JWT, before allowing access to a protected route.
If the user is authenticated, the middleware calls next() and passes control to the next middleware or route handler. Otherwise, it returns an unauthorized response.
function authMiddleware(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({
message: "Unauthorized user",
});
}
// token verification logic here
next();
}
app.get("/dashboard", authMiddleware, (req, res) => {
res.send("Welcome to dashboard");
});
Request Validation
Request validation middleware is generally used to validate incoming data in the request object, such as req.body, req.query, and req.params.
In these middleware functions, developers commonly use third-party validation libraries to ensure that the incoming data is correct, secure, and follows the expected format.
Some popular validation libraries are:
ZodJoiArkType
These libraries validate the incoming data before it reaches the main route handler. If the data is invalid, the middleware can immediately return an error response; otherwise, it passes control to the next middleware or route handler using next().
Conclusion
Express.js supports both custom and built-in middleware, and these middleware functions are used for tasks ranging from user authentication and data parsing to logging, validation, and performance optimization.
Understanding middleware and using it efficiently helps us build applications that are more robust, scalable, and maintainable.
I hope you enjoyed this blog and gained a clear understanding of how middleware works in Express.js.
