Route parameters are extracted from the URL path as strings - that's just how URLs work. But you often need more than a string: a numeric ID, a positive integer, a value from a fixed set or of a specific pattern, uuid, date, etc.
KosmoJS lets you express these requirements directly in the type system.
Params Refinements
Pass a tuple as the second type argument to defineRoute. Each position refines the corresponding parameter in route order.
For a route at api/users/[id]/index.ts:
import { defineRoute } from "_/api";
export default defineRoute<"users/[id]", [
number
]>(({ GET }) => [
GET(async (ctx) => {
const { id } = ctx.validated.params; // typed as number
}),
]);A request to /api/users/abc is rejected with a 400 before your handler runs.
Numeric params are coerced for you
A URL param arrives as a string, but when you type it as number, KosmoJS coerces it before validation - "123" becomes 123.
A non-numeric value like "abc" is left as-is and fails the number check, producing a clean 400.
So you write number (or a numeric VRefine) and read a real number from ctx.validated.params; no manual coercion needed.
Access validated parameters through ctx.validated.params - it carries the refined type, not the raw string. The underlying raw params still exists if you need the original:
- Hono -
ctx.req.param() - H3 -
event.context.params - Koa -
ctx.params
Refine further with VRefine (globally available, no import needed):
export default defineRoute<"users/[id]", [
VRefine<number, { minimum: 1, multipleOf: 1 }> // positive integer
]>(({ GET }) => [
// ...
]);Multiple Parameters
For routes with multiple parameters, each tuple position maps to the corresponding param. Positions are optional - omit any you don't need to refine.
export default defineRoute<"users/[id]/[view]", [
VRefine<number, { minimum: 1, multipleOf: 1 }>, // id
"profile" | "settings" | "data" // view
]>(({ GET }) => [
GET(async (ctx) => {
const { id, view } = ctx.validated.params;
}),
]);Refinements are positional, not name-based - renaming [id] to [userId] requires no changes here.