Node.js and Express course menu
Express Middleware Explained: next(), Order and Examples
Understand Express middleware: functions that run between the request and the response, calling next(), application-level and route-level middleware, built-in middleware such as express.json, writing logging, timing and API-key middleware, attaching data to req, and why order matters.
On this page
Middleware is the heart of Express. A middleware function receives the request and response, does something (logs, parses the body, checks a login, adds headers), and then either ends the response or calls next() to pass control to the next function in line. Route handlers are simply the last middleware in the chain.
request → [ express.json ] → [ logger ] → [ auth check ] → [ route handler ] → responseWriting middleware#
import express from "express";
const app = express();
function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next(); // continue to the next middleware or route
}
app.use(logger); // runs for every request
app.get("/", (req, res) => res.send("Home"));
app.get("/about", (req, res) => res.send("About us"));
app.listen(3000, () => console.log("Ready"));node logger.jsReadycurl -s localhost:3000/
curl -s localhost:3000/aboutHome
About usMeanwhile, the terminal running the server shows:
Ready
GET /
GET /aboutEach request was logged before its route ran.
A middleware function must do one of two things: send a response, or call next(). If it does neither, the request hangs.
Order matters#
Middleware runs in the order it is registered. Anything registered after a route that sends a response never runs for that route:
import express from "express";
const app = express();
app.use((req, res, next) => {
req.startedAt = Date.now(); // attach data for later middleware
res.setHeader("X-Shop", "ShopKart");
next();
});
app.get("/api/time", (req, res) => {
res.json({ handledIn: Date.now() - req.startedAt < 1000 ? "under a second" : "slow" });
});
app.use((req, res) => {
res.status(404).json({ error: `No route for ${req.method} ${req.path}` }); // runs only if nothing matched
});
app.listen(3000);curl -s -i localhost:3000/api/time | grep -E "^(HTTP|X-Shop)|handledIn"
curl -s localhost:3000/api/nothing-hereHTTP/1.1 200 OK
X-Shop: ShopKart
{"handledIn":"under a second"}
{"error":"No route for GET /api/nothing-here"}Two patterns from this example:
- Middleware can attach data to
req(req.startedAt, and laterreq.userafter a login check) for the next handlers. - A middleware at the end with no path acts as a catch-all 404 handler, because it only runs when no route responded.
Middleware for some routes only#
Pass middleware to a specific route, or use it on a path prefix:
import express from "express";
const app = express();
const API_KEY = "demo-key-123"; // in a real app: process.env.API_KEY
function requireApiKey(req, res, next) {
if (req.get("x-api-key") !== API_KEY) {
return res.status(401).json({ error: "Missing or invalid API key" });
}
next();
}
app.get("/api/public/menu", (req, res) => res.json(["Masala dosa", "Idli", "Filter coffee"]));
app.get("/api/admin/sales", requireApiKey, (req, res) => res.json({ today: 18450 }));
app.listen(3000);curl -s localhost:3000/api/public/menu
curl -s localhost:3000/api/admin/sales
curl -s -H "x-api-key: demo-key-123" localhost:3000/api/admin/sales["Masala dosa","Idli","Filter coffee"]
{"error":"Missing or invalid API key"}
{"today":18450}req.get(name) reads a request header (case-insensitively). Route-level middleware is how authentication protects only some routes. With a router, router.use(requireApiKey) protects every route in it.
Built-in and common middleware#
They are all installed the same way: app.use(middleware).
Middleware that runs after the response#
To measure how long requests take, listen for the response's finish event:
import express from "express";
const app = express();
app.use((req, res, next) => {
const start = process.hrtime.bigint();
res.on("finish", () => {
const ms = Number(process.hrtime.bigint() - start) / 1e6;
console.log(`${req.method} ${req.originalUrl} → ${res.statusCode} ${ms < 100 ? "(fast)" : "(slow)"}`);
});
next();
});
app.get("/api/ok", (req, res) => res.json({ ok: true }));
app.get("/api/missing", (req, res) => res.status(404).json({ error: "Not here" }));
app.listen(3000);curl -s localhost:3000/api/ok
curl -s localhost:3000/api/missing{"ok":true}
{"error":"Not here"}GET /api/ok → 200 (fast)
GET /api/missing → 404 (fast)Real logs record the exact duration; this example prints "fast" or "slow" so its output stays the same every run.
Common mistakes#
Practice#
Practice exercise
Write middleware requireJson that rejects POST requests whose Content-Type is not JSON with status 415 and { "error": "Send JSON" }, and apply it to POST /api/feedback, which echoes back the received body.
Show hint
req.is("application/json") returns a truthy value when the request's Content-Type is JSON.
Show solution
import express from "express";
const app = express();
app.use(express.json());
function requireJson(req, res, next) {
if (!req.is("application/json")) return res.status(415).json({ error: "Send JSON" });
next();
}
app.post("/api/feedback", requireJson, (req, res) => res.status(201).json({ received: req.body }));
app.listen(3000);curl -s -X POST localhost:3000/api/feedback -H "Content-Type: application/json" -d '{"rating":5}'
curl -s -X POST localhost:3000/api/feedback -d "rating=5"{"received":{"rating":5}}
{"error":"Send JSON"}curl's -d without a header sends application/x-www-form-urlencoded, so the second request is rejected.
Key takeaway#
Finished this lesson?