What Is Middleware?
Objectives
By the end of this lesson, you should be able to:
- Explain what a middleware function is and its three parameters
- Register middleware with
app.use() - Explain why calling
next()is required, and what happens if it’s forgotten
💡 Why this matters: Almost every non-trivial thing an Express app does, parsing a JSON body (Module 5), serving static files (Module 5), logging, authentication, runs through middleware. It’s the single most important pattern for understanding how a real Express app is actually built.
⚠️ A note on verification: every snippet and every response shown below was actually run and tested with real HTTP requests.
A Middleware Function
const express = require('express');
const app = express();
function logger(req, res, next) {
console.log(`[middleware] ${req.method} ${req.url}`);
next();
}
app.use(logger);
app.get('/', (req, res) => {
res.send('Homepage');
});
app.listen(4201);
curl http://localhost:4201/
Server-side log:
[middleware] GET /
Response:
Homepage
A middleware function takes three parameters, (req, res, next), the same req/res from every route handler (Module 5), plus next, a function that hands control to whatever comes after this middleware. app.use(middlewareFn) registers it to run on every incoming request, before any route handler.
Calling next() Is Required
app.use((req, res, next) => {
console.log('middleware ran, forgot to call next()');
// next() intentionally omitted
});
app.get('/', (req, res) => {
console.log('this never runs');
res.send('done');
});
curl http://localhost:4205/
(request hangs, no response ever arrives)
Without calling next(), Express has no way to know the middleware finished, the request simply hangs forever, the route handler never runs, and the client never gets a response. This is one of the most common real Express bugs, forgetting next() inside a middleware function that doesn’t itself send a response.
Middleware Runs in Registration Order
app.use((req, res, next) => {
console.log('step 1: first middleware');
next();
});
app.use((req, res, next) => {
console.log('step 2: second middleware');
next();
});
app.get('/', (req, res) => {
console.log('step 3: route handler');
res.send('done');
});
step 1: first middleware
step 2: second middleware
step 3: route handler
Each middleware runs, calls next(), and control passes to the next one registered, in order, a route handler is really just the last step in this same chain, the one that actually sends a response instead of calling next(). This ordered, pass-it-along design is called a pipeline, every request flows through it from top to bottom.
Try It
- Write a middleware function that logs the current timestamp (
new Date().toISOString()) for every request, register it withapp.use(), and confirm it logs on every route. - Temporarily remove the
next()call from your middleware, send a request, and confirm it hangs, then addnext()back. - Register two separate logging middleware functions, each printing a different message, and confirm both run, in order, before the route handler.
- Explain, in your own words, why a middleware function needs
nextas a parameter at all, when a route handler doesn’t.
Recap
- Middleware is a function with
(req, res, next), registered withapp.use(), running before route handlers. - Forgetting to call
next()(in middleware that doesn’t itself send a response) hangs the request forever. - Middleware runs in the order it’s registered, forming a pipeline every request flows through, a route handler is the final step in that same pipeline.
Next lesson: Express’s built-in middleware, express.json(), express.urlencoded(), and static file serving.