From "Just A Function" to Configurable Factories
Authoring Middleware
Write your own middleware. Plain functions for one-off, factories for reusable.
What you'll learn
- Author a basic middleware
- Use the factory pattern for configurable middleware
- Attach values to req
A middleware is just a function. Let’s go from trivial to production-ready.
A Simple Logger
function logger(req, res, next) {
console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);
next();
}
app.use(logger); Configurable — Factory Pattern
The same logger, but you can pick a log level:
function logger({ level = "info" } = {}) {
return (req, res, next) => {
console[level](`${new Date().toISOString()} ${req.method} ${req.url}`);
next();
};
}
app.use(logger({ level: "debug" })); The outer function takes config and returns the actual middleware.
Most third-party middleware uses this pattern — express.json(),
cors(), helmet() are all factory calls.
Attaching to req
A common pattern: middleware fetches something and attaches it for later handlers:
async function loadUser(req, res, next) {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) {
req.user = null;
return next();
}
try {
req.user = await verifyToken(token);
} catch {
req.user = null;
}
next();
}
app.use(loadUser);
app.get("/me", (req, res) => {
if (!req.user) return res.status(401).end();
res.json(req.user);
}); Subsequent handlers read req.user — no re-fetching, no
re-validating.
Capturing Response Behavior
For after-the-fact work, hook into res.on("finish"):
function timing(req, res, next) {
const start = Date.now();
res.on("finish", () => {
console.log(`${req.method} ${req.url} ${res.statusCode} ${Date.now() - start}ms`);
});
next();
} finish fires when the response is fully sent. Perfect for
metrics and logs.
Don’t Forget next()
The #1 middleware bug: forgetting to call next(). The request
hangs forever (or until the client times out).
function broken(req, res) {
console.log("hi");
// forgot next()! request never reaches the route handler
} If your middleware doesn’t respond, it must call next().
Async Middleware
In Express 5, async middleware works just like async routes — throw and the error middleware catches:
async function requireFreshSession(req, res, next) {
const session = await db.sessions.findById(req.sessionId);
if (!session || session.expiresAt < Date.now()) {
throw new Error("session expired");
}
req.session = session;
next();
}