Run Middleware Before Specific Routes or Route Groups
Route Middleware
Attach middleware to individual routes, to a path prefix with `router.use()`, or to a named parameter with `router.param()` to share logic without repeating yourself.
What you'll learn
- Attach inline middleware to a single route
- Apply scoped middleware to a path group with `router.use()`
- Intercept parameter extraction with `router.param()`
Middleware is not just an app-level concern. @koa/router lets you
attach middleware at three levels: a single route, a path prefix, or
a named parameter — so you only pay for the logic where you need it.
Per-Route Middleware
Pass one or more middleware functions before the final handler:
async function auth(ctx, next) {
if (!ctx.headers.authorization) {
ctx.status = 401;
ctx.body = { error: "unauthorized" };
return; // don't call next — stop here
}
await next();
}
router.get("/admin/dashboard", auth, async (ctx) => {
ctx.body = { dashboard: true };
});
router.get("/public/info", async (ctx) => {
ctx.body = { info: "open" }; // no auth middleware here
}); The auth function only runs for the /admin/dashboard route.
Multiple Middleware in Order
Stack as many as you need:
async function log(ctx, next) {
console.log(`${ctx.method} ${ctx.path}`);
await next();
}
async function validate(ctx, next) {
if (!ctx.request.body?.name) {
ctx.status = 400;
ctx.body = { error: "name required" };
return;
}
await next();
}
router.post("/users", log, validate, async (ctx) => {
ctx.status = 201;
ctx.body = { name: ctx.request.body.name };
}); router.use() — Scoped Middleware
Apply middleware to every route that starts with a path:
// Runs before all /admin/* routes
router.use("/admin", auth);
router.get("/admin/dashboard", async (ctx) => {
ctx.body = "dashboard";
});
router.get("/admin/settings", async (ctx) => {
ctx.body = "settings";
});
// Public route — auth does NOT run
router.get("/status", async (ctx) => {
ctx.body = "ok";
}); Pass no path to router.use() to run middleware before every route
on that router (similar to app.use()).
router.param() — Parameter Middleware
Run a function whenever a specific named parameter appears in the
matched route. Great for loading the entity once and attaching it
to ctx.state:
const db = new Map([["1", { id: "1", name: "Alice" }]]);
router.param("userId", async (id, ctx, next) => {
const user = db.get(id);
if (!user) {
ctx.status = 404;
ctx.body = { error: "user not found" };
return;
}
ctx.state.user = user; // attach for downstream handlers
await next();
});
router.get("/users/:userId", async (ctx) => {
ctx.body = ctx.state.user;
});
router.patch("/users/:userId", async (ctx) => {
ctx.state.user.name = "Updated";
ctx.body = ctx.state.user;
}); The lookup runs once per request and both routes share the result.
Summary
| Technique | Scope |
|---|---|
| Inline middleware | Single route |
router.use(path) | All routes under that path |
router.use() | All routes on the router |
router.param(key) | Any route containing :key |
Up Next
Sometimes a URL moves. The next lesson covers redirects and how to generate URLs from named routes.
Redirects →