Skip to main content
Madhukar
All Articles

What is Middleware in Express and How It Works

August 21, 20266 min read
nodejsbackendexpressjsweb-developmentjavascript
What is Middleware in Express and How It Works

A checkpoint between request and response

Every request that hits an Express server doesn’t go straight from “arrived” to “answered.” In between, it can pass through a series of checkpoints — each one inspecting, modifying, logging, or validating the request before deciding whether it should continue on to the next step. That series of checkpoints is middleware, and it’s one of the most important ideas to genuinely understand in Express.

What Middleware Is in Express

Middleware is a function that has access to the request, the response, and a special function called next() — sitting in between an incoming request and the route handler that ultimately responds to it.

function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next(); // pass control to the next step
}

app.use(logger);

Every middleware function follows this same basic shape: (req, res, next) => { ... } — it can look at or modify the request, do some work, and then either pass control forward with next(), or end the request-response cycle itself by sending a response.

Where Middleware Sits in the Request Lifecycle

A request entering an Express application doesn’t jump straight to its final route handler — it flows through every applicable middleware function first, in the order they were registered, before ever reaching the code that actually sends the final response.

This pipeline structure is exactly why middleware is so powerful: cross-cutting concerns — things that apply to many routes, like logging every request or checking authentication — can be handled once, centrally, instead of being repeated inside every individual route handler.

Types of Middleware

Application-level middleware

Registered directly on the Express app, applying broadly — either to every request, or to requests matching a specific path:

const app = express();

// Runs for every single request
app.use((req, res, next) => {
console.log("Request received");
next();
});

// Runs only for requests starting with /admin
app.use("/admin", (req, res, next) => {
console.log("Admin route accessed");
next();
});

Router-level middleware

Attached to a specific Express router instead of the whole app — useful for applying middleware only to a particular group of related routes:

const router = express.Router();

router.use((req, res, next) => {
console.log("This only runs for routes on this specific router");
next();
});

router.get("/profile", (req, res) => {
res.send("Profile page");
});

app.use("/users", router);

Built-in middleware

Express ships with a handful of middleware functions included directly, ready to use without installing anything extra:

app.use(express.json());          // parses incoming JSON request bodies
app.use(express.urlencoded()); // parses URL-encoded form data
app.use(express.static("public")); // serves static files from a folder

Execution Order of Middleware

Middleware runs in the exact order it’s registered in the code — this matters a great deal, and getting the order wrong is a common source of bugs.

app.use((req, res, next) => {
console.log("First");
next();
});

app.use((req, res, next) => {
console.log("Second");
next();
});

app.get("/", (req, res) => {
console.log("Third — the route handler itself");
res.send("Done");
});
First
Second
Third — the route handler itself

If authentication middleware, for instance, were registered after a route handler that needs it, that route would run completely unprotected — the order genuinely determines what protection or processing a given request actually receives.

Role of the next() Function

next() is what moves a request forward to the next middleware in the chain — without calling it, the request simply stops, hanging indefinitely, with no response ever sent.

app.use((req, res, next) => {
console.log("Middleware running");
// Forgetting next() here means the request just hangs forever
});
app.use((req, res, next) => {
console.log("Middleware running");
next(); // correctly passes control forward
});

next() can also be used to skip straight to error handling

app.use((req, res, next) => {
if (somethingWentWrong) {
return next(new Error("Something broke"));
}
next();
});

Calling next(err) with an argument tells Express to skip all remaining regular middleware and jump directly to error-handling middleware instead — a clean way to centralize error handling rather than manually managing it in every individual function.

Real-World Examples

Logging

function requestLogger(req, res, next) {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
}

app.use(requestLogger);

Applied once, at the application level, this logs every single request without needing to add logging code to each individual route.

Authentication

function requireAuth(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: "Not authenticated" });
}
// (verify the token here in a real implementation)
next();
}

app.get("/dashboard", requireAuth, (req, res) => {
res.send("Welcome to your dashboard");
});

Here, requireAuth is passed directly as middleware specific to one route — if there's no valid token, it responds immediately with 401 and never calls next(), meaning the actual route handler never runs at all.

Request validation

function validateUser(req, res, next) {
if (!req.body.name || !req.body.email) {
return res.status(400).json({ error: "Name and email are required" });
}
next();
}

app.post("/users", validateUser, (req, res) => {
res.status(201).json({ message: "User created" });
});

Validation middleware checks the incoming data before the actual route handler runs — keeping the route handler itself focused purely on its core logic, since it can trust the data has already been validated by the time it runs.

Final Takeaway

Middleware is Express’s way of letting you insert deliberate checkpoints between an incoming request and its final response — each one able to inspect, modify, log, reject, or validate along the way, before calling next() to pass control forward. Application-level middleware applies broadly; router-level middleware scopes that same idea to a specific group of routes; built-in middleware handles common needs like parsing JSON out of the box. Execution order determines exactly what protection and processing a request actually receives, and next() is the single mechanism tying every checkpoint in that chain together — forget it, and the request simply never completes.

Frequently Asked Questions

What happens if I never call next() inside a middleware function?

> The request hangs indefinitely — Express has no way of knowing to move forward, and no response is ever sent back to the client, which the client will typically experience as the request timing out.

Can middleware modify the request object before it reaches the route handler?

> Yes — this is a common, genuinely useful pattern. Middleware can attach data to req (like a decoded user object after verifying authentication) that the route handler can then read directly, without needing to redo that work itself.

Does middleware always have to call next(), or can it end the request itself?

> It can do either — middleware can call next() to pass control forward, or it can end the request-response cycle directly (like sending a 401 in the authentication example) without ever calling next(), if it determines the request shouldn't proceed any further.

Is there a difference between middleware order for app.use() and specific route middleware?

> The same core rule applies either way: Express runs middleware and route handlers in the exact order they’re registered in the code. Middleware passed directly to a specific route (like app.get("/dashboard", requireAuth, handler)) runs before that specific handler, in the order listed.

What is Middleware in Express and How It Works was originally published in Towards Dev on Medium, where people are continuing the conversation by highlighting and responding to this story.

Originally published by Mr Madhukar

Read the complete article on Medium with full formatting & reader responses.