Compose Multiple Routers to Split Routes by Resource
Nested Routers
Large apps split routes into separate router files. Mount child routers on a parent router to build a clean, modular structure.
What you'll learn
- Create separate routers for each resource
- Mount a child router onto a parent router
- Wire the combined router tree onto the Koa app
When a single file holds all your routes it becomes hard to read
and maintain. @koa/router lets you compose multiple router
instances so each resource lives in its own file.
One Router Per Resource
Create a dedicated router file for each area of your API:
// routes/users.js
import Router from "@koa/router";
const users = new Router();
users.get("/", async (ctx) => { ctx.body = "list users"; });
users.post("/", async (ctx) => { ctx.body = "create user"; });
users.get("/:id", async (ctx) => { ctx.body = `user ${ctx.params.id}`; });
export default users; // routes/posts.js
import Router from "@koa/router";
const posts = new Router();
posts.get("/", async (ctx) => { ctx.body = "list posts"; });
posts.post("/", async (ctx) => { ctx.body = "create post"; });
posts.get("/:id", async (ctx) => { ctx.body = `post ${ctx.params.id}`; });
export default posts; Composing with a Parent Router
Use router.use() to mount a child router at a sub-path:
// routes/index.js
import Router from "@koa/router";
import users from "./users.js";
import posts from "./posts.js";
const router = new Router();
router.use("/users", users.routes(), users.allowedMethods());
router.use("/posts", posts.routes(), posts.allowedMethods());
export default router; The child router’s paths are relative, so users.get("/") becomes
GET /users and users.get("/:id") becomes GET /users/:id.
Wiring the Root Router
In your entry file, mount only the root router:
// app.js
import Koa from "koa";
import router from "./routes/index.js";
const app = new Koa();
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, () => console.log("Listening on :3000")); Typical File Layout
| File | Responsibility |
|---|---|
app.js | Create app, mount root router |
routes/index.js | Compose all sub-routers |
routes/users.js | CRUD routes for /users |
routes/posts.js | CRUD routes for /posts |
routes/admin.js | Protected admin routes |
Sharing Middleware Within a Sub-Router
You can call router.use() on the child router itself to apply
middleware only to that resource:
// routes/admin.js
import Router from "@koa/router";
import { requireAdmin } from "../middleware/auth.js";
const admin = new Router();
admin.use(async (ctx, next) => {
// Every admin route runs this first
await requireAdmin(ctx, next);
});
admin.get("/dashboard", async (ctx) => { ctx.body = "dashboard"; });
export default admin; Up Next
Repeating a path prefix on every route is tedious. Router prefixes let you set it once.
Route Prefixes →