Middleware is a pattern for composing small, focused functions that each handle one concern, logging, authentication, error handling, and run in sequence for every request. This lesson introduces the pattern before you meet Express's implementation of it.
What Is Middleware?
Middleware is simply a function that sits in the request-handling pipeline: it receives the request (and response), does something with it, and then either passes control to the next function in the pipeline or ends the response itself. Chaining several small middleware functions lets you separate concerns cleanly instead of writing one giant handler per route.
Frameworks like Express formalize this with a next() callback, but the underlying idea, a pipeline of composable functions, can be built with plain Node.js as well.
function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next();
}
function runMiddleware(middlewares, req, res, finalHandler) {
let index = 0;
function next() {
const middleware = middlewares[index++];
if (middleware) {
middleware(req, res, next);
} else {
finalHandler(req, res);
}
}
next();
}
This tiny runMiddleware() helper mirrors what Express does internally: it calls each middleware in order, and each one decides whether to call next() to continue the chain.
The Middleware Signature
function middleware(req, res, next) {
// do something with req/res
next(); // pass control forward
// OR
res.end('done'); // end the response here, skip the rest
}
Middleware functions take (req, res, next) and decide whether to call next().
Calling next() passes control to the next middleware (or the final route handler).
Not calling next() (and not ending the response) leaves the request hanging.
Middleware runs in the exact order it was registered.
Middleware Concepts Cheatsheet
Core ideas behind the middleware pattern, regardless of framework.
Concept
Description
Pipeline
A sequence of functions each request passes through
next()
Passes control to the next function in the pipeline
Cross-cutting concerns
Logging, auth, CORS, are natural middleware candidates
Order matters
Middleware runs in the order it was registered
Short-circuiting
Ending the response early skips remaining middleware
Error middleware
A special case for catching and formatting errors
Why the Middleware Pattern Works So Well
Many concerns, logging every request, verifying authentication, parsing a body, apply to most or all routes identically. Without middleware, you'd repeat that logic inside every single route handler. Middleware extracts it once, into a small function you register a single time, and it runs consistently for every matching request.
Logging: record method, path, and timing for every request.
Authentication: verify a token before allowing access to protected routes.
Body parsing: convert a raw request stream into a usable req.body object.
Error handling: catch and format errors consistently across the whole app.
Why Registration Order Matters
Since middleware runs in the exact order it was added, a middleware that depends on something (like a parsed body, or an authenticated user) must be registered after whatever middleware produces that data, and before any route handler that needs it.
// Correct order
use(logger); // 1. log every request
use(parseJsonBody); // 2. parse the body
use(requireAuth); // 3. check authentication (needs parsed body for API keys, etc.)
// route handlers run last, after all middleware
Common Mistakes
Forgetting to call next(), silently hanging requests with no error message.
Calling next() after already sending a response, causing "headers already sent" errors.
Registering authentication middleware after routes that should have required it.
Duplicating the same logic (like logging) inside every individual route handler instead of extracting middleware.
Key Takeaways
Middleware is a pattern for composing small, ordered functions that each handle one concern.
Each middleware function decides whether to call next() or end the response itself.
Cross-cutting concerns (logging, auth, parsing) are ideal middleware candidates.
Registration order directly determines which data is available to later middleware and route handlers.
Pro Tip
Build this mental model with plain Node.js first, as shown above, before relying on Express's app.use(). Once you understand the raw pipeline, Express's middleware system will feel like a small, well-designed convenience layer rather than unexplained magic.
You now understand middleware conceptually. Next, meet Express.js, the framework that turns this pattern into a clean, production-ready API.