Pass an Array — or a Chain
Multiple Route Handlers
Routes can take multiple handler functions. Earlier ones run before later ones — a per-route middleware chain.
What you'll learn
- Chain handlers per route
- Author handlers that delegate via next()
- Use this for auth, validation, then controller
A route handler doesn’t have to be a single function. You can pass
any number — Express runs them in order, each calling next()
to pass control.
Comma-Separated
app.get("/admin", requireAuth, requireAdmin, listAdmins); Express runs requireAuth → requireAdmin → listAdmins. The
first two are middleware; the last is the controller.
Array
const adminMiddleware = [requireAuth, requireAdmin];
app.get("/admin", adminMiddleware, listAdmins); Arrays let you bundle middleware once and reuse it across routes.
A Validation + Controller Chain
import { z } from "zod";
function validate(schema) {
return (req, res, next) => {
const r = schema.safeParse(req.body);
if (!r.success) {
return res.status(400).json({ error: r.error.issues });
}
req.validBody = r.data;
next();
};
}
const CreateUserSchema = z.object({ name: z.string(), email: z.string().email() });
app.post("/users", validate(CreateUserSchema), (req, res) => {
// req.validBody is typed and validated
const user = createUser(req.validBody);
res.status(201).json(user);
}); How next() Works
Each handler calls next() to pass to the next in the chain. If
you forget — the request hangs. If you call res.send() and next(),
the next handler may try to send again and throw.
The rule: send a response, OR call next(). Never both.
function requireAuth(req, res, next) {
if (!req.headers.authorization) {
return res.status(401).end(); // sent — return here
}
next(); // OR pass to next
} return after sending so the function exits cleanly.
Skip the Rest With next("route")
To bail out of the current route and try the next matching route:
app.get("/users/:id", (req, res, next) => {
if (req.params.id === "me") next("route"); // try next route
else next();
}, (req, res) => {
res.json({ id: req.params.id });
});
app.get("/users/:id", (req, res) => {
res.json({ id: "current-user-id" });
}); Rarely needed — usually a sign you should restructure.
`app.route()` Chaining →