import * as koa from 'koa'; import { DefaultState, DefaultContext, Middleware, ParameterizedContext } from 'koa'; import { Key } from 'path-to-regexp'; /** * Extended middleware with param metadata * @internal */ type ParameterMiddleware = RouterMiddleware & { param?: string; _originalFn?: RouterParameterMiddleware; }; /** * Layer class represents a single route or middleware layer. * It handles path matching, parameter extraction, and middleware execution. * * @typeParam StateT - Custom state type extending Koa's DefaultState * @typeParam ContextT - Custom context type extending Koa's DefaultContext * @typeParam BodyT - Response body type */ declare class Layer { opts: LayerOptions; name: string | undefined; methods: string[]; paramNames: Key[]; stack: Array | ParameterMiddleware>; path: string | RegExp; regexp: RegExp; /** * Initialize a new routing Layer with given `method`, `path`, and `middleware`. * * @param path - Path string or regular expression * @param methods - Array of HTTP verbs * @param middleware - Layer callback/middleware or series of * @param opts - Layer options * @private */ constructor(path: string | RegExp, methods: string[], middleware: RouterMiddleware | Array>, options?: LayerOptions); /** * Normalize HTTP methods and add automatic HEAD support for GET * @private */ private _normalizeHttpMethods; /** * Normalize middleware to array and validate all are functions * @private */ private _normalizeAndValidateMiddleware; /** * Configure path matching regexp and parameters * @private */ private _configurePathMatching; /** * Configure path-to-regexp for string paths * @private */ private _configurePathToRegexp; /** * Returns whether request `path` matches route. * * @param path - Request path * @returns Whether path matches * @private */ match(path: string): boolean; /** * Returns map of URL parameters for given `path` and `paramNames`. * * @param _path - Request path (not used, kept for API compatibility) * @param captures - Captured values from regexp * @param existingParams - Existing params to merge with * @returns Parameter map * @private */ params(_path: string, captures: string[], existingParameters?: Record): Record; /** * Returns array of regexp url path captures. * * @param path - Request path * @returns Array of captured values * @private */ captures(path: string): string[]; /** * Generate URL for route using given `params`. * * @example * * ```javascript * const route = new Layer('/users/:id', ['GET'], fn); * * route.url({ id: 123 }); // => "/users/123" * ``` * * @param args - URL parameters (various formats supported) * @returns Generated URL * @throws Error if route path is a RegExp (cannot generate URL from RegExp) * @private */ url(...arguments_: unknown[]): string; /** * Parse url() arguments into params and options * Supports multiple call signatures: * - url({ id: 1 }) * - url(1, 2, 3) * - url({ query: {...} }) * - url({ id: 1 }, { query: {...} }) * @private */ private _parseUrlArguments; /** * Build parameter replacements for URL generation * @private */ private _buildParamReplacements; /** * Add query string to URL * @private */ private _addQueryString; /** * Run validations on route named parameters. * * @example * * ```javascript * router * .param('user', function (id, ctx, next) { * ctx.user = users[id]; * if (!ctx.user) return ctx.status = 404; * next(); * }) * .get('/users/:user', function (ctx, next) { * ctx.body = ctx.user; * }); * ``` * * @param paramName - Parameter name * @param paramHandler - Middleware function * @returns This layer instance * @private */ param(parameterName: string, parameterHandler: RouterParameterMiddleware): Layer; /** * Create param middleware with deduplication tracking * @private */ private _createParamMiddleware; /** * Insert param middleware at the correct position in the stack * @private */ private _insertParamMiddleware; /** * Prefix route path. * * @param prefixPath - Prefix to prepend * @returns This layer instance * @private */ setPrefix(prefixPath: string): Layer; /** * Apply prefix to the current path * @private */ private _applyPrefix; /** * Reconfigure path matching after prefix is applied * @private */ private _reconfigurePathMatching; } /** * Named constants for active router lifecycle events. * * Only events that are fully implemented appear here. Planned events will be * added as each one is implemented so the public API always reflects what * actually works. * * @experimental */ declare const RouterEvents: { /** * Fires when no route matched the request path + HTTP method. */ readonly NotFound: "not-found"; }; /** * Union of all valid router event name strings. * Derived from `RouterEvents` so the two are always in sync. * * @experimental */ type RouterEvent = (typeof RouterEvents)[keyof typeof RouterEvents]; /** * Accepts either a raw event name string or a selector function that receives * the `RouterEvents` object and returns an event name. * * Both forms are equivalent — choose whichever reads better in context. * * @example * ```typescript * // string * 'not-found' * * // selector function * (events) => events.NotFound * ``` * * @experimental */ type RouterEventSelector = RouterEvent | ((events: typeof RouterEvents) => RouterEvent); type RouterInstance = RouterImplementation; /** * Middleware with router property */ type RouterComposedMiddleware = Middleware> & { router?: Router; }; /** * @module koa-router */ declare class RouterImplementation { opts: RouterOptions; methods: string[]; exclusive: boolean; params: Record | RouterParameterMiddleware[]>; stack: Layer[]; host?: string | string[] | RegExp; /** * Event emitter for router lifecycle events (experimental) * @internal */ private _events; /** * Create a new router. * * @example * * Basic usage: * * ```javascript * const Koa = require('koa'); * const Router = require('@koa/router'); * * const app = new Koa(); * const router = new Router(); * * router.get('/', (ctx, next) => { * // ctx.router available * }); * * app * .use(router.routes()) * .use(router.allowedMethods()); * ``` * * @alias module:koa-router * @param opts - Router options * @constructor */ constructor(options?: RouterOptions); /** * Generate URL from url pattern and given `params`. * * @example * * ```javascript * const url = Router.url('/users/:id', {id: 1}); * // => "/users/1" * ``` * * @param path - URL pattern * @param args - URL parameters * @returns Generated URL */ static url(path: string | RegExp, ...arguments_: unknown[]): string; /** * Use given middleware. * * Middleware run in the order they are defined by `.use()`. They are invoked * sequentially, requests start at the first middleware and work their way * "down" the middleware stack. * * @example * * ```javascript * // session middleware will run before authorize * router * .use(session()) * .use(authorize()); * * // use middleware only with given path * router.use('/users', userAuth()); * * // or with an array of paths * router.use(['/users', '/admin'], userAuth()); * * app.use(router.routes()); * ``` * * @param middleware - Middleware functions * @returns This router instance */ use(middleware: RouterMiddleware): Router; use(middleware: RouterComposedMiddleware): Router; use(...middleware: Array>): Router; use(...middleware: Array>): Router; use(path: string | RegExp | string[], middleware: RouterMiddleware): Router; use(path: string | RegExp | string[], middleware: RouterComposedMiddleware): Router; use(path: string | RegExp | string[], ...middleware: Array>): Router; use(path: string | RegExp | string[], ...middleware: Array>): Router; use(path: string | RegExp | string[], m1: RouterMiddleware, m2: RouterMiddleware | RouterComposedMiddleware, ...middleware: Array | RouterComposedMiddleware>): Router; use(m1: RouterMiddleware, m2: RouterMiddleware | RouterComposedMiddleware, ...middleware: Array | RouterComposedMiddleware>): Router; /** * Check if first argument is an array of paths (all elements must be strings) * @private */ private _isPathArray; /** * Check if first argument is an explicit path (string or RegExp) * Empty string counts as explicit path to enable param capture * @private */ private _hasExplicitPath; /** * Check if middleware contains a nested router * @private */ private _isNestedRouter; /** * Apply middleware to multiple paths * @private */ private _useWithPathArray; /** * Mount a nested router * @private */ private _mountNestedRouter; /** * Clone a router instance * @private */ private _cloneRouter; /** * Clone a layer instance (deep clone to avoid shared references) * @private */ private _cloneLayer; /** * Apply this router's param middleware to a nested router * @private */ private _applyParamMiddlewareToRouter; /** * Register regular middleware (not nested router) * @private */ private _registerMiddleware; /** * Set the path prefix for a Router instance that was already initialized. * Note: Calling this method multiple times will replace the prefix, not stack them. * * @example * * ```javascript * router.prefix('/things/:thing_id') * ``` * * @param prefixPath - Prefix string * @returns This router instance */ prefix(prefixPath: string): Router; /** * Returns router middleware which dispatches a route matching the request. * * @returns Router middleware */ middleware(): RouterComposedMiddleware; /** * Get the request path to use for routing * @private */ private _getRequestPath; /** * Store matched routes on context * @private */ private _storeMatchedRoutes; /** * Set matched route information on context * @private */ private _setMatchedRouteInfo; /** * Build middleware chain from matched layers * @private */ private _buildMiddlewareChain; routes(): RouterComposedMiddleware; /** * Register an event handler on the router. * * @experimental This API is experimental and may change in future versions. * * **Active events:** * - `'not-found'` — fires when no route matched path + method. Handlers are * composed and called instead of `next()`, so they are responsible for * calling `next()` themselves if they want to pass control downstream. * * @example * * All three forms are equivalent: * * ```javascript * import { RouterEvents } from '@koa/router'; * * // Named constant (recommended — autocomplete + refactor safe) * router.on(RouterEvents.NotFound, handler); * * // Selector function (fluent style) * router.on((events) => events.NotFound, handler); * * // Raw string (still accepted) * router.on('not-found', handler); * ``` * * Multiple handlers compose in registration order: * * ```javascript * router.on(RouterEvents.NotFound, async (ctx, next) => { * console.log('no route matched', ctx.path); * await next(); * }); * router.on(RouterEvents.NotFound, (ctx) => { * ctx.status = 404; * ctx.body = { error: 'Not Found' }; * }); * ``` * * @param event - Event name string, `RouterEvents` constant, or selector * function `(events) => events.NotFound` (see `RouterEventSelector`) * @param handler - Middleware to run when the event fires * @returns This router instance for chaining */ on(event: RouterEventSelector, handler: RouterMiddleware): Router; /** * Returns separate middleware for responding to `OPTIONS` requests with * an `Allow` header containing the allowed methods, as well as responding * with `405 Method Not Allowed` and `501 Not Implemented` as appropriate. * * @example * * ```javascript * const Koa = require('koa'); * const Router = require('@koa/router'); * * const app = new Koa(); * const router = new Router(); * * app.use(router.routes()); * app.use(router.allowedMethods()); * ``` * * **Example with [Boom](https://github.com/hapijs/boom)** * * ```javascript * const Koa = require('koa'); * const Router = require('@koa/router'); * const Boom = require('boom'); * * const app = new Koa(); * const router = new Router(); * * app.use(router.routes()); * app.use(router.allowedMethods({ * throw: true, * notImplemented: () => new Boom.notImplemented(), * methodNotAllowed: () => new Boom.methodNotAllowed() * })); * ``` * * @param options - Options object * @returns Middleware function */ allowedMethods(options?: AllowedMethodsOptions): RouterMiddleware; /** * Check if we should process allowed methods * @private */ private _shouldProcessAllowedMethods; /** * Collect all allowed methods from matched routes * @private */ private _collectAllowedMethods; /** * Handle 501 Not Implemented response * @private */ private _handleNotImplemented; /** * Handle OPTIONS request * @private */ private _handleOptionsRequest; /** * Handle 405 Method Not Allowed response * @private */ private _handleMethodNotAllowed; /** * Register route with all methods. * * @param args - Route arguments (name, path, middleware) * @returns This router instance */ all(name: string, path: string | RegExp, ...middleware: Array>): Router; all(path: string | RegExp | Array, ...middleware: Array>): Router; /** * Redirect `source` to `destination` URL with optional 30x status `code`. * * Both `source` and `destination` can be route names. * * ```javascript * router.redirect('/login', 'sign-in'); * ``` * * This is equivalent to: * * ```javascript * router.all('/login', ctx => { * ctx.redirect('/sign-in'); * ctx.status = 301; * }); * ``` * * @param source - URL or route name * @param destination - URL or route name * @param code - HTTP status code (default: 301) * @returns This router instance */ redirect(source: string | symbol, destination: string | symbol, code?: number): Router; /** * Create and register a route. * * @param path - Path string * @param methods - Array of HTTP verbs * @param middleware - Middleware functions * @param additionalOptions - Additional options * @returns Created layer * @private */ register(path: string | RegExp | string[], methods: string[], middleware: RouterMiddleware | RouterMiddleware[], additionalOptions?: LayerOptions): Layer | Router; /** * Register multiple paths with the same configuration * @private */ private _registerMultiplePaths; /** * Create a route layer with given configuration * @private */ private _createRouteLayer; /** * Lookup route with given `name`. * * @param name - Route name * @returns Matched layer or false */ route(name: string): Layer | false; /** * Generate URL for route. Takes a route name and map of named `params`. * * @example * * ```javascript * router.get('user', '/users/:id', (ctx, next) => { * // ... * }); * * router.url('user', 3); * // => "/users/3" * * router.url('user', { id: 3 }); * // => "/users/3" * * router.use((ctx, next) => { * // redirect to named route * ctx.redirect(ctx.router.url('sign-in')); * }) * * router.url('user', { id: 3 }, { query: { limit: 1 } }); * // => "/users/3?limit=1" * * router.url('user', { id: 3 }, { query: "limit=1" }); * // => "/users/3?limit=1" * ``` * * @param name - Route name * @param args - URL parameters * @returns Generated URL or Error */ url(name: string, ...arguments_: unknown[]): string | Error; /** * Match given `path` and return corresponding routes. * * @param path - Request path * @param method - HTTP method * @returns Match result with matched layers * @private */ match(path: string, method: string): MatchResult; /** * Match given `input` to allowed host * @param input - Host to check * @returns Whether host matches */ matchHost(input?: string): boolean; /** * Run middleware for named route parameters. Useful for auto-loading or * validation. * * @example * * ```javascript * router * .param('user', (id, ctx, next) => { * ctx.user = users[id]; * if (!ctx.user) return ctx.status = 404; * return next(); * }) * .get('/users/:user', ctx => { * ctx.body = ctx.user; * }) * .get('/users/:user/friends', ctx => { * return ctx.user.getFriends().then(function(friends) { * ctx.body = friends; * }); * }) * // /users/3 => {"id": 3, "name": "Alex"} * // /users/3/friends => [{"id": 4, "name": "TJ"}] * ``` * * @param param - Parameter name * @param middleware - Parameter middleware * @returns This router instance */ param(parameter: string, middleware: RouterParameterMiddleware): Router; /** * Helper method for registering HTTP verb routes * @internal - Used by dynamically added HTTP methods */ _registerMethod(method: string, ...arguments_: unknown[]): Router; /** * HTTP GET method */ get(name: string, path: string | RegExp, ...middleware: Array>): Router; get(path: string | RegExp | Array, ...middleware: Array>): Router; /** * HTTP POST method */ post(name: string, path: string | RegExp, ...middleware: Array>): Router; post(path: string | RegExp | Array, ...middleware: Array>): Router; /** * HTTP PUT method */ put(name: string, path: string | RegExp, ...middleware: Array>): Router; put(path: string | RegExp | Array, ...middleware: Array>): Router; /** * HTTP PATCH method */ patch(name: string, path: string | RegExp, ...middleware: Array>): Router; patch(path: string | RegExp | Array, ...middleware: Array>): Router; /** * HTTP DELETE method */ delete(name: string, path: string | RegExp, ...middleware: Array>): Router; delete(path: string | RegExp | Array, ...middleware: Array>): Router; /** * HTTP DELETE method alias (del) */ del(name: string, path: string | RegExp, ...middleware: Array>): Router; del(path: string | RegExp | Array, ...middleware: Array>): Router; /** * HTTP HEAD method */ head(name: string, path: string | RegExp, ...middleware: Array>): Router; head(path: string | RegExp | Array, ...middleware: Array>): Router; /** * HTTP OPTIONS method */ options(name: string, path: string | RegExp, ...middleware: Array>): Router; options(path: string | RegExp | Array, ...middleware: Array>): Router; /** * Dynamic HTTP method handler for any method from http.METHODS. * Use this index signature to access methods like PURGE, COPY, CONNECT, TRACE, etc. * These are dynamically added at runtime from Node's http.METHODS. * * @example * ```typescript * // Type-safe way to use dynamic methods * const router = new Router(); * router.register('/cache/:key', ['PURGE'], middleware); * * // Or cast to access dynamic method directly * (router as any).purge('/cache/:key', middleware); * ``` */ [method: string]: unknown; } /** * Router constructor interface with automatic type inference for custom HTTP methods. * * @example * ```typescript * // Methods are automatically typed based on what you pass * const router = new Router({ * methods: ['GET', 'POST', 'PURGE', 'CUSTOM'] as const * }); * * // TypeScript knows these methods exist * router.get('/users', handler); * router.purge('/cache/:key', handler); * router.custom('/special', handler); * ``` */ interface RouterConstructor { new (options: RouterOptionsWithMethods): RouterWithMethods; new (options?: RouterOptions): Router; /** * Generate URL from url pattern and given `params`. */ url(path: string, parameters?: Record): string; url(path: string, ...parameters: unknown[]): string; readonly prototype: Router; } type Router = RouterInstance; declare const Router: RouterConstructor; /** * Type definitions for @koa/router */ type RouterOptions = { /** * Control which route is executed when multiple routes match a request. * * - `true` — run only the last registered matching route (legacy behaviour) * - `'specificity'` — run only the most specific matching route, defined as * the one with the fewest path parameters. This is OpenAPI-compliant * (see https://spec.openapis.org/oas/v3.0.3#path-templating-matching) and * is the recommended mode for code generated from OpenAPI specifications. * When two routes have an equal number of parameters the last registered * one wins, preserving deterministic behaviour. * - `false` / omitted — all matching routes run in registration order */ exclusive?: boolean | 'specificity'; /** * Prefix for all routes */ prefix?: string; /** * Host for router match (string, array of strings, or RegExp) * - string: exact match * - string[]: matches if input equals any string in the array * - RegExp: pattern match */ host?: string | string[] | RegExp; /** * HTTP methods this router should respond to */ methods?: string[]; /** * Path to use for routing (internal) */ routerPath?: string; /** * Whether to use case-sensitive routing */ sensitive?: boolean; /** * Whether trailing slashes are significant */ strict?: boolean; /** * Additional options passed through */ [key: string]: unknown; }; type LayerOptions = { /** * Route name for URL generation */ name?: string | null; /** * Case sensitive routing */ sensitive?: boolean; /** * Require trailing slash */ strict?: boolean; /** * Whether trailing slashes matter (path-to-regexp v8) */ trailing?: boolean; /** * Route path ends at this path */ end?: boolean; /** * Prefix for the route */ prefix?: string; /** * Ignore captures in route matching */ ignoreCaptures?: boolean; /** * Ignore a generated wildcard route parameter when populating ctx.params * * @internal */ ignoreWildcardParameter?: string | number; /** * Treat path as a regular expression */ pathAsRegExp?: boolean; /** * Additional options passed through to path-to-regexp */ [key: string]: unknown; }; type UrlOptions = { /** * Query string parameters */ query?: Record | string; [key: string]: unknown; }; type RouterParameterContext = { /** * URL parameters */ params: Record; /** * Router instance */ router: RouterInstance; /** * Matched route path (internal) */ _matchedRoute?: string | RegExp; /** * Matched route name (internal) */ _matchedRouteName?: string; }; type RouterParameterMiddleware = (parameterValue: string, context: RouterContext, next: () => Promise) => unknown | Promise; type MatchResult = { /** * Layers that matched the path */ path: Layer[]; /** * Layers that matched both path and HTTP method */ pathAndMethod: Layer[]; /** * Whether a route (not just middleware) was matched */ route: boolean; }; type AllowedMethodsOptions = { /** * Throw error instead of setting status and header */ throw?: boolean; /** * Throw the returned value in place of the default NotImplemented error */ notImplemented?: () => Error; /** * Throw the returned value in place of the default MethodNotAllowed error */ methodNotAllowed?: () => Error; }; /** * Extended Koa context with router-specific properties * Matches the structure from @types/koa-router */ type RouterContext = ParameterizedContext, BodyT> & { /** * Request with params (set by router during routing) */ request: { params: Record; }; /** * Path of matched route */ routerPath?: string; /** * Name of matched route */ routerName?: string; /** * Array of matched layers */ matched?: Layer[]; /** * Whether a route (with HTTP methods) was matched for this request. * Set by the router before any handlers run. * * Use this in app-level middleware after `router.routes()` to detect * requests that the router did not handle. * * @example * ```javascript * app.use(router.routes()); * app.use((ctx) => { * if (!ctx.routeMatched) { * ctx.status = 404; * ctx.body = { error: 'Not Found' }; * } * }); * ``` */ routeMatched?: boolean; /** * Captured values from path */ captures?: string[]; /** * New router path (for nested routers) */ newRouterPath?: string; /** * Track param middleware execution (internal) */ _matchedParams?: WeakMap; }; /** * Router middleware function type */ type RouterMiddleware = Middleware, BodyT>; /** * HTTP method names in lowercase */ type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'del' | 'head' | 'options' | 'connect' | 'trace' | string; /** * Router options with generic methods array for type inference */ type RouterOptionsWithMethods = Omit & { methods?: readonly M[]; }; /** * Type for a dynamic HTTP method function on Router */ type RouterMethodFunction = { (name: string, path: string | RegExp, ...middleware: Array>): RouterInstance; (path: string | RegExp | Array, ...middleware: Array>): RouterInstance; }; /** * Router with additional HTTP methods based on the methods option. * Use createRouter() factory function for automatic type inference. */ type RouterWithMethods = RouterInstance & Record, RouterMethodFunction>; /** * Parameter validation helpers. * * These utilities exist to help migrate legacy `:param(regex)` usage from older * router/path-to-regexp versions to v14+ (path-to-regexp v8), where inline * parameter regexes are no longer supported in route strings. */ /** * Options for createParameterValidationMiddleware helper */ type ParameterValidationOptions = { /** * HTTP status to use when the value does not match * @default 400 */ status?: number; /** * Error message to use when the value does not match * @default `Invalid value for parameter ""` */ message?: string; /** * Whether the error message should be exposed to the client. * Passed through to HttpError#expose. */ expose?: boolean; /** * Optional custom error factory. If provided, it is used * instead of the default HttpError. */ createError?: (parameterName: string, value: string) => Error; }; /** * Convenience helper to recreate legacy `:param(regex)` validation. * * @example * const validateUuid = createParameterValidationMiddleware('id', uuidRegex); * router.param('id', validateUuid).get('/role/:id', handler); * router.get('/role/:id', createParameterValidationMiddleware('id', uuidRegex) */ declare function createParameterValidationMiddleware(parameterName: string, pattern: RegExp, options?: ParameterValidationOptions): RouterMiddleware & RouterParameterMiddleware; export { type AllowedMethodsOptions, type HttpMethod, Layer, type LayerOptions, type MatchResult, type ParameterValidationOptions, Router, type RouterContext, type RouterEvent, type RouterEventSelector, RouterEvents, type RouterInstance, type RouterMethodFunction, type RouterMiddleware, type RouterOptions, type RouterOptionsWithMethods, type RouterParameterMiddleware, type RouterWithMethods, type UrlOptions, createParameterValidationMiddleware, Router as default };