| name | koapp-router |
| description | Define routes, path parameters, validators, and nested routers with the @axiosleo/koapp Router class. Use when declaring HTTP/WebSocket/TCP routes in koapp, adding path params like /users/{:id}, composing nested routers, attaching per-route middlewares or after-handlers, validating params/query/body with validatorjs rules, or using shortcut helpers (get/post/put/patch/delete/any). |
@axiosleo/koapp Router
Source: src/router.js.
Router is the single route-definition primitive for all three application
types (KoaApplication, SocketApplication, WebSocketApplication). A
router can be flat or a tree of sub-routers, and it carries middlewares,
handlers, after-handlers, and validators.
Constructor
const { Router } = require('@axiosleo/koapp');
const router = new Router(prefix, options);
| Arg | Type | Default | Notes |
|---|
prefix | string | '' | Path prefix like /api/v1 or /users/{:id} |
options.method | string | '' | HTTP method. Use 'any' for all methods. Empty string disables the route |
options.handlers | Function[] | [] | Async handler functions async (context) => { ... } |
options.middlewares | Function[] | [] | Run before handlers |
options.afters | Function[] | [] | Run after handlers (even on thrown response) |
options.validators | object | {} | { params, query, body } - each { rules, messages? } |
Any unknown keys on options are kept on the router instance as-is.
Path params
Use {:name} for path parameters. Legacy :name also works but {:name}
is preferred:
new Router('/users/{:id}/posts/{:postId}', {
method: 'GET',
handlers: [async (context) => {
const { id, postId } = context.params;
}]
});
Wildcards:
/* - matches one segment (captured as params.<next> if named)
/** - matches one segment without capture
/*** - matches any remaining path, ideal for fallback handlers
HTTP method shortcuts
Each method shortcut creates a nested sub-router:
const root = new Router('/api');
root.get('/users', listUsers);
root.post('/users', createUser, {
body: { rules: { name: 'required|string', email: 'required|email' } }
});
root.put('/users/{:id}', updateUser);
root.patch('/users/{:id}', patchUser);
root.delete('/users/{:id}', deleteUser);
root.any('/health', healthcheck);
Under the hood every shortcut delegates to router.push(method, prefix, handler, validator).
Custom methods
root.push('copy', '/resources/{:id}', async (context) => { ... });
The framework uppercases and stores the method; match it by sending the
same method string.
Combining multiple methods
root.push('get|post', '/multi', async (context) => {
});
Nested routers
const api = new Router('/api');
const v1 = new Router('/v1');
const users = new Router('/users');
users.get('/{:id}', async (context) => { ... });
v1.add(users);
api.add(v1);
router.add(...subrouters) attaches other Router instances. If the first
argument is a string prefix, sub-routers are added under it:
api.add('/admin', adminUserRouter, adminRoleRouter);
The helper router.new(prefix, options) creates and mounts a sub-router
in one call:
api.new('/internal', { middlewares: [authMiddleware] });
Middlewares and afters
const root = new Router(null, {
middlewares: [
async (context) => {
console.log(`[${context.method}] ${context.pathinfo}`);
}
],
afters: [
async (context) => {
console.log('response:', context.response);
}
]
});
- Middlewares inherit down the tree (children + grandchildren see them)
- Afters also inherit and run after any handler in the subtree
- Throwing inside a handler still runs afters
Request validation
Validation uses validatorjs rules.
root.new('/items/{:id}', {
method: 'PUT',
handlers: [updateItem],
validators: {
params: {
rules: { id: 'required|integer' }
},
query: {
rules: { include: 'in:profile,settings' }
},
body: {
rules: {
name: 'required|string',
price: 'required|numeric|min:0'
},
messages: {
required: 'The :attribute field is required.'
}
}
}
});
On failure the framework throws HttpError(400, <first-error-message>)
which the workflow converts into a 400 response.
Common validatorjs rules: required, string, integer, numeric, email,
url, min:N, max:N, in:a,b,c, regex:/pattern/, boolean, array.
The catch-all fallback
To handle anything that did not match:
root.any('/***', async (context) => {
result('Not Found', 404, { 'Content-Type': 'text/plain' });
});
'/***' matches any remaining path. The framework falls back to the
deepest matching /*** route when no exact match is found.
Method resolution order
For a request PUT /users/42:
- Walk the router tree by path segments, preferring static matches over
* / ** / ***
- When multiple candidates remain, prefer the one whose
method list includes the request method (or ANY)
- If nothing matches, follow the nearest
/*** fallback
- If even that misses, the framework lets the next Koa middleware run (typically 404)
Common pitfalls
- Leaving
method: '' (or unset) on a router.push creates a route that
matches no HTTP method - requests fall through to fallback handlers.
Always use 'any' or a concrete method unless you know you want this.
new Router('/') and new Router('') behave differently; prefer null
or '' for the root.
- Path params must use
{:name} inside route segments - {:name} must
be the entire segment, not a substring (/a-{:x} does not work).
- Validators bubble with the router; nested routers inherit parent middlewares
and afters, so avoid double-registering auth middlewares.
See also
- Richer, copy-paste examples: examples.md
- Building a complete HTTP server around the router: koapp-apps
- Sending responses from handlers: koapp-response