Route Prefixes

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.

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

TechniqueBest for
Constructor prefixVersioned bases, single-file routers
Nested routerPer-resource files with shared sub-path
Both combinedVersioned 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 →