Common patterns and recipes for building real-world applications with @koa/router.
| Recipe | Description |
|---|---|
| TypeScript Recipe | Full TypeScript example showcasing type inference - no explicit types needed! |
| Nested Routes | Production-ready nested router patterns with multiple levels (3+ deep) |
| RESTful API Structure | Organize your API with nested routers for clean separation |
| Authentication & Authorization | JWT-based authentication with role-based access control |
| Request Validation | Validate request data with Joi middleware |
| Parameter Validation | Validate and transform URL parameters using router.param() |
| Regex Parameter Validation | Validate URL parameters with regex (replacement for :param(regex) in v14+) |
| API Versioning | Implement API versioning with multiple routers |
| Error Handling | Centralized error handling with custom error classes |
| Pagination | Pagination middleware with configurable limits and metadata |
| Health Checks | Health check, readiness, and liveness probe endpoints |
The router now provides automatic type inference - no explicit type annotations needed:
import Router from '@koa/router';
const router = new Router();
// ✅ ctx and next are automatically inferred!
router.get('/users/:id', async (ctx, next) => {
ctx.params.id; // ✅ Inferred as string
ctx.request.params; // ✅ Inferred as Record<string, string>
ctx.body = { ... }; // ✅ Works
return next(); // ✅ Works
});
// ✅ Also works for router.use()
router.use(async (ctx, next) => {
ctx.state.startTime = Date.now();
return next();
});See the TypeScript Recipe for complete examples.
Each recipe folder contains:
[recipe-name].ts- The recipe implementation with full documentation[recipe-name].test.ts- Comprehensive tests demonstrating usage
# Run all recipe tests
yarn test:recipes
# Run a specific recipe test
yarn test:recipes -- --test-name-pattern="Authentication"import {
authenticate,
requireRole,
requireAnyRole
} from './recipes/authentication-authorization';
// Require authentication only
router.get('/profile', authenticate, getProfile);
// Require specific role
router.get('/admin', authenticate, requireRole('admin'), adminHandler);
// Require any of multiple roles
router.get(
'/moderate',
authenticate,
requireAnyRole('admin', 'moderator'),
modHandler
);import { paginate, buildPaginatedResponse } from './recipes/pagination';
// Use default pagination (10 items per page)
router.get('/users', paginate(), getUsers);
// Custom pagination limits
router.get('/posts', paginate({ defaultLimit: 20, maxLimit: 50 }), getPosts);import {
errorHandler,
NotFoundError,
ValidationError
} from './recipes/error-handling';
// Global error handler (add first)
app.use(errorHandler);
// Throw typed errors in routes
router.get('/users/:id', async (ctx) => {
const user = await User.findById(ctx.params.id);
if (!user) {
throw new NotFoundError('User', ctx.params.id);
}
ctx.body = user;
});import { authenticate } from './recipes/authentication-authorization';
import { paginate } from './recipes/pagination';
import { validate } from './recipes/request-validation';
router
.get('/users', authenticate, paginate(), getUsers)
.post('/users', authenticate, validate(createUserSchema), createUser);If you have a useful recipe pattern, feel free to add it to this directory!