@koa/router: a typed router middleware for Koa
Router middleware for Koa.
At a glance
- What is it?
- @koa/router is the router middleware for Koa, written in TypeScript with type definitions included, offering Express style routing, named parameters, named routes, host matching, nested routers and path-to-regexp v8 matching, with 405 and 501 handling built in. It requires Node 20 or newer and Koa 2 or 3, and the v15 line is a rewrite that carries a migration guide.
- Who is it for?
- Adopt @koa/router if you are building a Koa service and want the Express style routing model with full TypeScript inference instead of hand rolling a middleware chain, since the type parameters for ctx.state and the custom context are what make it usable in a typed codebase. Do not adopt it if you need the pre-v15 behaviour or the old package name, because v15 is a rewrite and the repository carries a dedicated full migration document.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Express style routing, rewritten in TypeScript
@koa/router describes itself as a modern TypeScript router middleware for Koa, maintained by Forward Email and Lad. The positioning is deliberate: it gives you the routing model most Node developers already know while being written in TypeScript with type definitions shipped in the package, so no separate @types packages are needed.
The feature list is long and worth scanning for the ones that are not obvious.
Express style routing is there, with app.get, app.post, app.put and the rest. Named URL parameters extract values from the path, and named routes generate URLs from a route name rather than a hand written string. Host matching selects routes by hostname, which is how you serve several domains from one app. HEAD request support is automatic for GET routes.
Multiple middleware can be chained per route, routers can be nested inside routers, and regular expression paths are allowed for flexible matching. Parameter middleware runs for specific URL parameters, so you can validate an id before the handler does any work.
Two HTTP correctness details are automatic. A 405 Method Not Allowed is returned when the path matches but the method does not, and 501 Not Implemented is used for the proper case. Neither is something you want to hand code on every route.
Types that infer rather than annotate
The TypeScript support is the headline feature and it goes further than most routers in the Koa ecosystem.
The basic case needs no annotation at all. ctx and next are inferred automatically in route handlers, so ctx.params.id is a string without you declaring it, and the same is true for router.use callbacks.
import Router from '@koa/router';
const router = new Router();
// ctx and next are automatically inferred!
router.get('/:id', (ctx, next) => {
const id = ctx.params.id; // ✅ Inferred as string
ctx.request.params.id; // ✅ Also available
ctx.body = { id }; // ✅ Works
return next(); // ✅ Works
});
// Also works for router.use()
router.use((ctx, next) => {
ctx.state.startTime = Date.now();
return next();
});Explicit types are still available when you want them, using the RouterContext type exported from the package together with Next from koa.
import Router, { RouterContext } from '@koa/router';
import type { Next } from 'koa';
router.get('/:id', (ctx: RouterContext, next: Next) => {
const id = ctx.params.id;
ctx.body = { id };
});The parameter middleware type is exported too, as RouterParameterMiddleware, which is what you cast to when you want type safety on a param handler that validates the value and throws a 400 for a malformed id.
Generics for application state and custom context
The feature that goes beyond typical router typings is the generic support, which lets you declare the shape of ctx.state and of your own context properties once and have them respected everywhere.
You define an AppState interface for your application state and an AppContext interface for your custom context, then construct the router with those type parameters.
import Router, { RouterContext } from '@koa/router';
import type { Next } from 'koa';
// Define your application state
interface AppState {
user?: {
id: string;
email: string;
};
}
// Define your custom context
interface AppContext {
requestId: string;
}
// Create router with generics
const router = new Router<AppState, AppContext>();After that, ctx.state.user is fully typed and ctx.requestId is a string rather than any.
You can also push the types down to individual routes rather than the whole router, which is the more useful direction when one route needs a different shape of state.
router.get<UserState, UserContext>(
'/users/:id',
async (ctx: RouterContext<UserState, UserContext>) => {
// ctx.state.user is fully typed
// ctx.permissions is fully typed
ctx.body = {
user: ctx.state.user,
permissions: ctx.permissions
};
}
);The set of exported names is broader than Router and RouterContext. The package also exports RouterOptions, RouterMiddleware, RouterParameterMiddleware, AllowedMethodsOptions, UrlOptions, HttpMethod, MatchResult, LayerOptions, Layer, and the event types RouterEvent, RouterEventSelector and RouterEvents.
The quick start and the routing model in practice
The wiring is the Koa model: the router is a middleware, and you apply both its routes and its allowedMethods to the app.
import Koa from 'koa';
import Router from '@koa/router';
const app = new Koa();
const router = new Router();
// Define routes
router.get('/', (ctx, next) => {
ctx.body = 'Hello World!';
});
router.get('/users/:id', (ctx, next) => {
ctx.body = { id: ctx.params.id };
});
// Apply router middleware
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);The two calls at the end are the part people forget. router.routes() dispatches to a matching handler, and router.allowedMethods() is what produces the 405 response when the path matches but the method does not, and the 501 for the case where the method is not implemented at all. Without the second call you lose both.
The path matching uses path-to-regexp v8, which the feature list calls modern and predictable. That is a meaningful detail for anyone upgrading, because the path syntax that path-to-regexp accepted in older versions changed, and a pattern that used to work may now be invalid. The router depends on path-to-regexp at version 8.4.2 or newer.
The matching is done on the named parameter syntax, and the feature list also promises clean ctx.params, meaning only the URL parameters you defined are present and no internal routing keys leak into the object. That is a small thing that avoids a class of bug where application code reads a property it did not think the router had put there.
Dependencies, build output and the release cadence
The dependency list is short and tells you the shape of the implementation. The runtime dependencies are debug, http-errors, koa-compose and path-to-regexp. koa-compose is what runs a chain of middleware functions in the Koa way, and http-errors is what produces the thrown HTTP errors you raise with ctx.throw.
Koa itself is a peer dependency rather than a direct one, declared as ^2.0.0 || ^3.0.0, and the peerDependenciesMeta marks it as not optional. So the router will not pull in a Koa version for you, and it supports both the 2.x and 3.x lines.
The build uses tsup and outputs three things. The main entry is ./dist/index.js for CommonJS, the module entry is ./dist/index.mjs for ESM, and the types entry is ./dist/index.d.ts. The exports map then resolves the import and require conditions separately, with a .d.mts declaration file for the ESM side.
Node 20 or newer is required, and the README says it is tested on v20, v22, v24 and v25. The engines field in the package file agrees.
The release history is steady. The listed versions are v15.7.0 from 2026-07-04, v15.6.0 from 2026-05-30 and v15.5.0 from 2026-05-02, so roughly one minor release every two months on the 15.x line. The repository is not archived and the last push was on 2026-10-16.
The v15 rewrite and what to check before upgrading
The repository root contains a file named FULL_MIGRATION_TO_V15+.md, and its existence is the clearest signal that v15 is not a routine release.
Two changes are visible in the documentation and both affect existing code. The first is path-to-regexp v8, which changes how path patterns are written and parsed. If your app has routes with unusual patterns, the ones that used to parse may not parse now, and the failure appears at startup rather than at request time.
The second is the TypeScript surface itself. v15 is written in TypeScript and the package ships its own declarations, so the types available to your handlers changed shape. Code that annotated ctx loosely, or that relied on the old @types/koa-router structure, will now be checked differently. The feature list notes compatibility with @types/koa-router in terms of matching the official type structure, which is reassuring but not the same as saying old code compiles unchanged.
The repository also has dedicated tooling for this. There are several tsconfig files, including tsconfig.strict-typecheck.json, tsconfig.typecheck.json, tsconfig.bench.json, tsconfig.recipes.json, tsconfig.test.json and tsconfig.ts-node.json, so the type checking of the package itself is run in several modes rather than one. The bench/ directory holds benchmarks and the recipes/ directory holds the recipe examples referenced from the README, with its own tsconfig.
The README's table of contents lists sections for API documentation, advanced features, best practices, recipes, performance, testing and migration guides, and the migration guides entry is where the full v15 document belongs.
Editorial conclusion
Adopt @koa/router if you are building a Koa service and want the Express style routing model with full TypeScript inference instead of hand rolling a middleware chain, since the type parameters for ctx.state and the custom context are what make it usable in a typed codebase. Do not adopt it if you need the pre-v15 behaviour or the old package name, because v15 is a rewrite and the repository carries a dedicated full migration document. Verify first that your Node version is 20 or newer and your Koa is 2 or 3, that you read the v15 migration guide before upgrading an existing app, and that you check whether path-to-regexp v8 changes affect any route patterns you rely on, since the matching semantics changed.
Frequently asked questions
How do I install @koa/router and what does it need?
Run npm install @koa/router, or yarn add @koa/router. It requires Node.js 20 or newer, tested on v20, v22, v24 and v25, and Koa 2.0.0 or newer as a peer dependency. Koa is declared as a peer dependency and is not optional.
How do I get TypeScript inference for ctx.params in @koa/router?
Write the route handler without annotations. ctx and next are inferred automatically, so ctx.params.id is a string and ctx.state respects your state type. For custom state and context shapes, use generic type parameters on the router or on individual route methods with Router<AppState, AppContext> and RouterContext.
What does router.allowedMethods() do in Koa?
It adds automatic 405 Method Not Allowed responses when the request path matches a route but the method does not, and 501 Not Implemented for methods that are not implemented. It is applied alongside router.routes(), for example app.use(router.routes()).use(router.allowedMethods()).
What changed in @koa/router v15?
The v15 line is a rewrite, and the repository ships a dedicated full migration document, FULL_MIGRATION_TO_V15+.md. Visible changes include the move to path-to-regexp v8 for path matching and the new TypeScript type definitions shipped with the package, so both route patterns and type annotations in existing code need checking.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/koajs-router)Was this useful?