Routing
Learn how to define routes and handle different URL patterns in your Kori application. Kori wraps Hono's SmartRouter, so routing behavior is essentially the same as Hono's.
Basic Routing
Kori provides intuitive methods for common HTTP verbs:
typescript
const app = createKori();
// GET route
app.get('/users', (ctx) => {
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];
return ctx.res.json({ users });
});
// POST route
app.post('/users', async (ctx) => {
const body = await ctx.req.bodyJson();
const newUser = createUser(body);
return ctx.res.json({ user: newUser });
});
// PUT route
app.put('/users/:id', async (ctx) => {
const { id } = ctx.req.pathParams();
const body = await ctx.req.bodyJson();
const user = await updateUser(id, body);
return ctx.res.json({ user });
});
// DELETE route
app.delete('/users/:id', async (ctx) => {
const { id } = ctx.req.pathParams();
await deleteUser(id);
return ctx.res.empty();
});
// PATCH route
app.patch('/users/:id', async (ctx) => {
const { id } = ctx.req.pathParams();
const body = await ctx.req.bodyJson();
const user = await partialUpdateUser(id, body);
return ctx.res.json({ user });
});
Kori also supports .head()
and .options()
methods for handling HEAD and OPTIONS requests.
Path Parameters
Kori automatically infers path parameter names at compile time for better IDE support:
typescript
// Single parameter - TypeScript infers { id: string }
app.get('/users/:id', (ctx) => {
// ctx.req.pathParams() is typed as { id: string }
const { id } = ctx.req.pathParams(); // id is string
return ctx.res.json({ userId: id });
});
// Multiple parameters - automatically inferred
app.get('/users/:userId/posts/:postId', (ctx) => {
// ctx.req.pathParams() is typed as { userId: string, postId: string }
const { userId, postId } = ctx.req.pathParams();
return ctx.res.json({
userId, // string
postId, // string
message: `Post ${postId} by user ${userId}`,
});
});
// Optional parameters (with ?)
app.get('/search/:query/:page?', (ctx) => {
// ctx.req.pathParams() is typed as { query: string, page?: string }
const { query, page } = ctx.req.pathParams();
const pageNumber = page ? parseInt(page) : 1;
return ctx.res.json({ query, page: pageNumber });
});
// Custom regex patterns
app.get('/files/:id{[0-9]+}', (ctx) => {
// ctx.req.pathParams() is typed as { id: string }
const { id } = ctx.req.pathParams(); // id will match only digits
return ctx.res.json({ fileId: id });
});
The type inference works by parsing the route string at compile time and extracting parameter names, helping prevent typos and improve IDE autocompletion.
Route Groups and Children
Create modular route groups using createChild()
:
typescript
// Create API v1 routes
const apiV1 = app.createChild({
prefix: '/api/v1',
configure: (k) =>
k
.get('/status', (ctx) => {
return ctx.res.json({ version: 'v1', status: 'stable' });
})
.get('/users', (ctx) => {
return ctx.res.json({ users: getUsersV1() });
}),
});
// Create API v2 routes with different behavior
const apiV2 = app.createChild({
prefix: '/api/v2',
configure: (k) =>
k
.onRequest((ctx) => {
// Add request logging for v2 only
ctx.log().info('API v2 request', { path: ctx.req.url().pathname });
return ctx; // Must return context
})
.get('/status', (ctx) => {
return ctx.res.json({
version: 'v2',
status: 'beta',
features: ['enhanced-validation', 'better-errors'],
});
})
.get('/users', (ctx) => {
return ctx.res.json({ users: getUsersV2() });
}),
});
// Admin routes with authentication
const adminRoutes = app.createChild({
prefix: '/admin',
configure: (k) =>
k
.onRequest((ctx) => {
const token = ctx.req.header('authorization')?.replace('Bearer ', '');
if (!token || !isValidAdminToken(token)) {
throw new Error('Unauthorized');
}
return ctx.withReq({ isAdmin: true });
})
.onError((ctx, err) => {
if (err instanceof Error && err.message === 'Unauthorized') {
return ctx.res.unauthorized({ message: 'Admin access required' });
}
})
.get('/dashboard', (ctx) => {
return ctx.res.json({ dashboard: 'admin data' });
}),
});
Route Registration Order
Routes are matched in the order they are registered. More specific routes should be defined before less specific ones:
typescript
// ✅ Correct: specific routes first
app.get('/users/me', (ctx) => {
return ctx.res.json({ user: getCurrentUser(ctx) });
});
app.get('/users/:id', (ctx) => {
const { id } = ctx.req.pathParams();
return ctx.res.json({ user: getUserById(id) });
});
// ❌ Incorrect: this would never be reached
// app.get('/users/:id', handler);
// app.get('/users/me', handler); // Never matched!