Nested Routers

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.

3 min read Level 2/5 #koa#routing#nested
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

FileResponsibility
app.jsCreate app, mount root router
routes/index.jsCompose all sub-routers
routes/users.jsCRUD routes for /users
routes/posts.jsCRUD routes for /posts
routes/admin.jsProtected 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 →