Middleware: The Request Pipeline
Every Express request flows through a pipeline of functions, each deciding whether to pass control forward. Understanding next() is the entire mental model.
What you'll learn
- Explain what middleware is and how next() controls the request pipeline
- Implement a simple middleware pipeline runner to make the mental model concrete
- Identify why middleware order matters, the same way route order does
Prerequisites
Explanation
Every Express request handler you've written so far — (req, res) => {...} — is actually a special case of something more general: middleware, a function of the shape (req, res, next) => {...}. A route handler is middleware that (usually) ends the pipeline by sending a response. Ordinary middleware — logging, authentication checks, request parsing — does some work and then calls next() to pass control to whatever comes after it in the pipeline, or doesn't call it at all if it needs to stop the request there (sending an error response instead, for instance).
This is the entire mental model: a request flows through an ordered sequence of functions, each of which can inspect or modify req/res, and each of which decides whether to call next() and let the request continue, or handle it and stop. express.json() (used in the previous lesson) is middleware: it parses the request body and calls next() so the route handler after it can read req.body. A logging middleware might log the request and immediately call next(). An authentication-check middleware might call next() if the request is authenticated, or respond with a 401 and not call next() if it isn't — stopping the pipeline right there, before the route handler that expected an authenticated user ever runs.
Order matters here for exactly the same reason it mattered for routes: middleware registered with app.use(...) applies, in order, to every request that reaches it. Registering a body-parsing middleware after a route that reads req.body means that route sees undefined, because its own middleware never ran before it. Registering an authentication check after the routes it's meant to protect means it protects nothing — the routes already ran and responded before the check ever executed.
Forgetting to call next() in a middleware that isn't itself ending the request (not sending a response) is one of the most common real bugs — the request simply hangs forever, with no response and no error, because nothing downstream in the pipeline ever runs.
Example
A real, working middleware-pipeline runner -- the exact 'chain of functions calling next()' mechanism Express itself implements.
function runPipeline(middlewares, req, res) {
let index = 0;
function next() {
const middleware = middlewares[index];
index += 1;
if (middleware) middleware(req, res, next);
}
next();
}
const logger = (req, res, next) => {
console.log("Request to:", req.path);
next();
};
const respond = (req, res) => {
res.body = "Hello from " + req.path;
};
runPipeline([logger, respond], { path: "/courses" }, {});Try it yourself
Add a third middleware BEFORE respond that stops the pipeline early (doesn't call next()) -- notice respond never runs.
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Guided exercise
Guided exercise
Using runPipeline already defined, write an authCheck middleware that calls next() only if req.isAuthenticated is true; otherwise it should set res.status = 401 and NOT call next(). Then run the pipeline [authCheck, respond] with an unauthenticated request and confirm respond never ran.
Checks: sets the 401 status for an unauthenticated request · stops the pipeline, never reaching respond
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Independent exercise
Independent exercise
Write buildRequestLog(middlewareNames) that returns the array of middleware names IN THE ORDER they would actually execute, given that middleware runs strictly in the order provided -- this models why registration order determines execution order.
Checks: preserves registration order as execution order
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Common mistakes
- Forgetting to call next() in a middleware that isn't meant to end the request, causing the request to hang forever with no response.
- Registering an authentication-check middleware AFTER the routes it's supposed to protect, so it never actually runs before those routes respond.
- Calling next() AND also sending a response in the same middleware, which can cause 'headers already sent' errors when the pipeline continues to another handler that also tries to respond.
Knowledge check
Takeaway
Middleware is a pipeline of functions each deciding whether to call next() and continue, or stop the request there — and middleware order matters for exactly the same reason route order does: everything executes in registration order.
Summary
This lesson built a real middleware-pipeline runner to make the next()-based mental model concrete, and covered the common bugs from forgetting next() or misordering middleware relative to the routes it should protect.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.