Set a Base Path Once Instead of Repeating It on Every Route
Route Prefixes
Pass `prefix` to the `Router` constructor (or call `router.prefix()`) to prepend a shared base path to every route — ideal for API versioning.
What you'll learn
- Create a router with a constructor prefix
- Apply a prefix dynamically with `router.prefix()`
- Use prefixes for API version namespacing
Without a prefix you would write /api/v1/users, /api/v1/posts,
/api/v1/orders on every route. A router prefix moves that repeated
segment into one place.
Constructor Prefix
Pass the prefix option when creating the router:
import Router from "@koa/router";
const router = new Router({ prefix: "/api/v1" });
router.get("/users", async (ctx) => { ctx.body = "v1 users"; });
router.get("/posts", async (ctx) => { ctx.body = "v1 posts"; }); These register as GET /api/v1/users and GET /api/v1/posts.
router.prefix() Method
You can also set the prefix after construction — useful when the prefix is determined at runtime (e.g. from config):
const router = new Router();
router.prefix("/api/v2");
router.get("/users", async (ctx) => { ctx.body = "v2 users"; }); Call router.prefix() before registering routes; it does not
retroactively update already-registered routes.
API Versioning Pattern
Run two versioned routers side by side:
import Koa from "koa";
import Router from "@koa/router";
const app = new Koa();
const v1 = new Router({ prefix: "/api/v1" });
v1.get("/users", async (ctx) => { ctx.body = { version: 1, users: [] }; });
const v2 = new Router({ prefix: "/api/v2" });
v2.get("/users", async (ctx) => {
ctx.body = { version: 2, data: [], meta: { total: 0 } };
});
app
.use(v1.routes()).use(v1.allowedMethods())
.use(v2.routes()).use(v2.allowedMethods());
app.listen(3000); Both versions live independently. You can deprecate v1 without touching v2 routes.
Prefix vs Nested Router
| Technique | Best for |
|---|---|
| Constructor prefix | Versioned bases, single-file routers |
| Nested router | Per-resource files with shared sub-path |
| Both combined | Versioned API split into resource files |
Combined Example
// routes/v1/users.js
import Router from "@koa/router";
const users = new Router();
users.get("/", async (ctx) => { ctx.body = "v1 user list"; });
export default users;
// routes/v1/index.js
import Router from "@koa/router";
import users from "./users.js";
const v1 = new Router({ prefix: "/api/v1" });
v1.use("/users", users.routes(), users.allowedMethods());
export default v1; Up Next
Individual routes can run their own middleware before the main handler — authentication checks, validation, caching, and more.
Route Middleware →