Capture Dynamic Segments From the URL Path
Route Parameters
Named segments like `:id` capture variable parts of the URL and expose them on `ctx.params` inside your handler.
What you'll learn
- Define named route parameters with the `:param` syntax
- Read captured values from `ctx.params`
- Work with multiple and optional parameters
A route parameter is a named placeholder in the path pattern. When
a request URL matches the pattern, @koa/router extracts the
placeholder’s value and puts it in ctx.params.
Basic Named Parameter
Prefix a path segment with : to make it a parameter:
router.get("/users/:id", async (ctx) => {
const { id } = ctx.params;
ctx.body = { userId: id };
}); A GET /users/42 request sets ctx.params.id to "42". Values
are always strings — coerce to a number when needed.
Multiple Parameters
You can use as many named segments as you like:
router.get("/orgs/:orgId/repos/:repoId", async (ctx) => {
const { orgId, repoId } = ctx.params;
ctx.body = { orgId, repoId };
}); GET /orgs/acme/repos/api-server gives
{ orgId: "acme", repoId: "api-server" }.
Optional Parameters
Append ? to make a segment optional:
router.get("/posts/:year/:month?", async (ctx) => {
const { year, month } = ctx.params;
if (month) {
ctx.body = { year, month };
} else {
ctx.body = { year, month: "all" };
}
}); Both /posts/2024 and /posts/2024/06 will match.
Wildcard Catch-All
Use (.*) to match everything after a prefix:
router.get("/files/(.*)", async (ctx) => {
ctx.body = { file: ctx.params[0] };
}); /files/assets/logo.svg → ctx.params[0] is "assets/logo.svg".
Full Example
import Koa from "koa";
import Router from "@koa/router";
const app = new Koa();
const router = new Router();
const db = {
"1": { id: "1", name: "Alice" },
"2": { id: "2", name: "Bob" },
};
router.get("/users/:id", async (ctx) => {
const user = db[ctx.params.id];
if (!user) {
ctx.status = 404;
ctx.body = { error: "user not found" };
return;
}
ctx.body = user;
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000); Parameter Constraints
@koa/router uses path-to-regexp under the hood. You can add
inline regex constraints:
// Only match numeric IDs
router.get("/users/:id(\\d+)", async (ctx) => {
ctx.body = { id: Number(ctx.params.id) };
}); Non-numeric IDs will fall through to the next matching route (or produce a 404).
Up Next
Query strings let callers pass optional filters and options without changing the URL structure.
Query Strings →