What is Middleware in Express and How It Works

In Express.js, middleware is one of the most important concepts for building scalable, flexible web applications and APIs. Middleware lets you plug into the request/response cycle and run logic before requests reach your main route handlers or before responses are sent back to clients.
Think of middleware as checkpoints or assembly-line stations: every incoming request passes through one or more middleware functions before getting a response.
Let’s break down what middleware is, where it fits in Express, and see practical examples for common use cases.
What Is Middleware in Express?
A middleware in Express is simply a function that receives the request and response objects (just like a route handler), but can do three main things:
Read or modify the request before it reaches the route handler.
Read or modify the response before it goes back to the client.
Decide whether the request should continue, pass control to the next middleware, or end the request/response cycle.
Basic Middleware Signature:
function myMiddleware(req, res, next) {
// ...logic here
next(); // Pass control to the next middleware or route
}
Notice the third argument: next. This is a special function provided by Express to move the request along the pipeline.
Where Middleware Sits in the Request Lifecycle
When a request hits your Express app:
The request enters Express.
It passes through any configured middleware functions in order.
Eventually it reaches a route handler (if not ended early by middleware).
The response is sent and the cycle ends.
Analogy:
Imagine a package traveling through an assembly line.
Each station (middleware) can check it, add a sticker, reject it, wrap it, or send it to the next station.
Finally, the package is delivered (response sent back).
Types of Middleware in Express
Express supports several types of middleware, each suited for different use cases.
1. Application-Level Middleware
These are functions applied to the whole app.
Example: Logging Middleware
const express = require("express");
const app = express();
app.use((req, res, next) => {
console.log(`\({req.method} \){req.url}`);
next();
});
app.use()applies the middleware to every request.You must call
next()to let the pipeline continue!
2. Router-Level Middleware
Middleware attached to specific routers or sets of routes.
Example: Protect Admin Routes
const express = require("express");
const adminRouter = express.Router();
adminRouter.use((req, res, next) => {
if (!req.user || !req.user.isAdmin) {
return res.status(403).send("Forbidden");
}
next();
});
adminRouter.get("/dashboard", (req, res) => {
res.send("Admin Dashboard");
});
You attach the router to the app (app.use("/admin", adminRouter);) and only /admin routes will use that middleware.
3. Built-In Middleware
Express provides built-in middleware for common needs:
express.json()– Automatically parses incoming JSON requests.express.urlencoded()– Parses URL-encoded form data.express.static()– Serves static files like images or CSS.
Example:
app.use(express.json());
Now every incoming JSON body is parsed and accessible as req.body.
Execution Order of Middleware in Express
Order matters!
Express runs middleware in the order you define it.
app.use(middlewareA);
app.use(middlewareB);
app.get("/hello", routeHandler);
A request goes to
middlewareAfirst, thenmiddlewareB, thenrouteHandler.If any middleware does not call
next(), the request halts immediately and never reaches the route.
Middleware can:
Continue to the next function (
next())End the response (
res.send())Pass errors to the next error handler (
next(err))
Role of the next() Function
next()is how you tell Express to “move on” to the next thing in the pipeline.Without
next(), the request will hang forever (unless you send a response).You can call
next(err)to signal an error and invoke error-handling middleware.
Middleware Execution Sequence: A Visual Example
Consider:
app.use((req, res, next) => {
console.log("First middleware");
next();
});
app.use((req, res, next) => {
console.log("Second middleware");
next();
});
app.get("/", (req, res) => {
res.send("Done!");
});
Request to / will print:
First middleware
Second middleware
And then respond with Done!.
If you send a response inside middleware (e.g., res.send("403 Forbidden")), Express skips all the remaining middleware and routes.
Real-World Examples of Middleware
1. Logging All Requests
app.use((req, res, next) => {
console.log(`\({req.method} \){req.url} at ${new Date()}`);
next();
});
2. Authentication “Checkpoint”
function authenticate(req, res, next) {
if (!req.user) {
return res.status(401).send("Unauthorized");
}
next();
}
app.use("/dashboard", authenticate);
3. Request Validation
function validateUserInput(req, res, next) {
if (!req.body.username || !req.body.password) {
return res.status(400).json({ error: "Missing fields" });
}
next();
}
app.post("/signup", validateUserInput, (req, res) => {
// Create user
});
If validation fails, middleware ends the cycle with a
400response.If it passes, the actual signup route runs.
Request Pipeline Analogy
Picture every request passing through a pipeline of functions:
Middleware 1: Add user info if logged in.
Middleware 2: Validate the input.
Middleware 3: Log the request.
Route Handler: Process the request and send the response.
Each middleware can check, modify, or even stop the request before it ultimately reaches the actual route handler.
Best Practices & Notes
Place global middleware (like logging, JSON parsing) as early as possible.
Use router-level middleware for features needed only on some route groups (like admin auth).
Always call
next()unless you send a response or hit an error.Order of middleware is critical—earlier middleware can affect later middleware and route handlers.
Middleware can also return errors or responses, which halts the pipeline.
Conclusion
Middleware in Express works as a series of checkpoints between an incoming request and the final response. It allows you to:
Add features and behavior to your app without changing route handlers
Reuse common processing logic (like authentication, logging, input validation)
Control the flow of requests and responses
By mastering middleware, you gain powerful control over every request in your Express application, making your code modular, testable, and maintainable.





