npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

koa-use-decorator-router

v1.2.1

Published

基于 koa 和 @koa/router 的装饰器路由中间件 / A Koa plugin for @koa/router that allows you to use decorators to define routes

Readme

一个基于装饰器的 Koa 路由封装,提供控制器抽象、参数注入和单例管理

A decorator-driven Koa routing framework with controller abstraction, parameter injection, and singleton support.

依赖 koa@koa/routerreflect-metadata

Requires koa, @koa/router, and reflect-metadata.

此插件使用的装饰器是 TypeScript 的功能,如果使用 JavaScript,需要确保能正确编译为 TypeScript 风格的装饰器产物(如 Babel),包括参数装饰器。

This plugin relies on TypeScript decorators. If you are using JavaScript, you must ensure your build toolchain (e.g., Babel) supports legacy/TypeScript-style decorators, including parameter decorators.

提供基于 Koa 的装饰器路由,使路由定义更简洁以及更易读。

Provides decorator-based routing for Koa, making route definitions cleaner and more readable.

安装 / Install

npm install koa-use-decorator-router

依赖要求 / Requirements

  • koa >=2.0.0
  • @koa/router >=10.0.0
  • reflect-metadata >=0.2.0

tsconfig.json:

  • "experimentalDecorators": true
  • "emitDecoratorMetadata": true

使用 / Usage

目录下的控制器文件名必须以 Controller 结尾。可以通过 acceptAnyControllerName 允许任何控制器文件名。

The controller file name must end with Controller. You can allow any controller file name by setting acceptAnyControllerName: true

目录下的控制器文件必须导出一个被 @Controller 装饰器装饰的类。

The controller file must export a class decorated with @Controller.

  • 声明 / Declaration

ESM Module

import Koa from 'koa';
import { decorator } from 'koa-use-decorator-router';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const app = new Koa();
app.use(
	decorator({
		controllerDir: resolve(dirname(fileURLToPath(import.meta.url)), './controller'),
		allowedMethods: true,
	}),
);

CommonJS Module

const Koa = require('koa');
const decorator = require('koa-use-decorator-router');
const { dirname, resolve } = require('node:path');
const { fileURLToPath } = require('node:url');

const app = new Koa();
app.use(
	decorator({
		controllerDir: resolve(__dirname, './controller'),
		allowedMethods: true,
	}),
);

函数创建中间件 / Create Middleware via Function

扩展传参方式 / Extended parameter options

import Koa from 'koa';
import Router from '@koa/router';
import { decorator } from 'koa-use-decorator-router';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const dir = resolve(dirname(fileURLToPath(import.meta.url)), './controller');

const app = new Koa();
const router = new Router();
// 可以传递 router 实例,来自定义插件内部的路由实例
// You can pass a router instance to customize the internal router instance.
app.use(decorator(dir, router)).use(router.allowedMethods());

构造函数创建中间件 / Create Middleware via Constructor

Decorator 实例可以直接调用 Router 实例的方法,内部通过代理模式实现。

You can directly call methods of Router instance on Decorator instance. The methods are proxied internally.

import Koa from 'koa';
import Router from '@koa/router';

import { Decorator } from 'koa-use-decorator-router';
// ======= or =======
import Decorator from 'koa-use-decorator-router';

import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const dir = resolve(dirname(fileURLToPath(import.meta.url)), './controller');

const app = new Koa();

const decorator = new Decorator(dir);
// 可以直接使用 `Router` 实例中的 `prefix` 方法
// You can directly call `prefix` method of `Router` instance on `Decorator` instance.
decorator.prefix('/api');
app.use(decorator.middleware()).use(decorator.allowedMethods());

// 自定义路由实例 / Customize Router Instance
const router = new Router();
const decorator = new Decorator(dir, router);
app.use(decorator.middleware()).use(decorator.allowedMethods());
  • 基本声明示例 / Basic Declaration Example

成员函数返回的数据会作为响应体返回。

成员函数可以使用 @Context 注入 Koa.Context 类型的参数。

The return value of the member function will be used as the response body.

The member function can use @Context to inject a Koa.Context object.

import type Koa from 'koa';
import { Controller, HttpMethod, Context } from 'koa-use-decorator-router';

@Controller('/home')
export class HomeController {
	@HttpMethod.Get('/')
	async index(@Context() ctx: Koa.Context) {
		console.log(ctx.query);
		return 'Hello World!';
	}
}
  • 单例 / Singleton

单例装饰器不会修改或重写类的构造函数,而是在内部维护一个单例实例,插件生命周期内会复用该实例。

非控制器目录下的其它类也可以使用 @Singleton 装饰器。

The @Singleton decorator does not modify or override the class constructor. Instead, it internally manages a single shared instance, which is reused whenever the class is instantiated within the plugin lifecycle.

Classes outside the controller directory can also use the @Singleton decorator.

import { Singleton, Controller, HttpMethod } from 'koa-use-decorator-router';

@Singleton()
@Controller('/home')
export class HomeController {
	@HttpMethod.Get('/')
	async index() {
		return 'Hello World!';
	}
}

// HomeService.ts
import { Singleton } from 'koa-use-decorator-router';

@Singleton()
export class HomeService {
	async index() {
		return 'Hello World!';
	}
}
  • 参数注入示例 / Parameter Injection Example

@Inject 装饰器第二个参数可以是一个枚举值,也可以是一个函数。

  • 预定义枚举值(如 Types.Int)用于内置类型转换
  • 自定义函数用于转换参数值

@Inject decorator second parameter can be an enum value or a function.

  • a predefined enum (e.g., Types.Int) for built-in type conversion
  • a custom function for transforming the value
import { Controller, HttpMethod, Inject, Types } from 'koa-use-decorator-router';

@Controller('/home')
export class HomeController {
	@HttpMethod.Get('/:name')
	async string(@Inject('name') name: string) {
		return `Hello ${name}`;
	}

	@HttpMethod.Get('/:id')
	async int(@Inject('id', Types.Int) id: number) {
		return `Hello ${id}`;
	}

	@HttpMethod.Get('/:path')
	async custom(@Inject('path', decodeURIComponent) path: string) {
		return `Hello ${path}`;
	}
}
  • 响应头示例 / Response Header Example

@ResponseHeader 装饰器用于设置响应头,第一个参数是响应头名称,第二个参数是响应头值。

The @ResponseHeader decorator is used to set response headers. The first parameter specifies the header name, and the second parameter specifies the header value.

import { Controller, HttpMethod, ResponseHeader, Methods } from 'koa-use-decorator-router';

@Controller('/home')
export class HomeController {
	@HttpMethod.Get('/')
	@ResponseHeader('Content-Type', 'text/plain')
	async index() {
		return 'Hello World!';
	}
}
  • 跨域 / CORS

该库提供一个 @Cors 装饰器用于处理跨域(CORS)配置,可作用在控制器类(class-level)或成员方法(method-level)上。

装饰器的行为参考并借鉴了 @koa/cors 的实现,但本项目会自动为配置了 @Cors 的路由创建对应的 OPTIONS 预检路由,并返回相应的 CORS 响应头。 如果在成员方法上使用 @Cors,则 Access-Control-Allow-Methods 将会强制设置为当前路由方法,因此在方法级别传递 methods 参数是无效的。

This library provides a @Cors decorator for handling Cross-Origin Resource Sharing (CORS) configurations, which can be applied at the controller class level or the individual method level.

The behavior of this decorator is inspired by the implementation of @koa/cors. However, this project automatically creates the corresponding OPTIONS preflight route for any route configured with @Cors and returns the appropriate CORS response headers. When @Cors is applied at the method level, the Access-Control-Allow-Methods header is forcibly set to the current route’s method. Therefore, passing a methods parameter at the method level has no effect.

参数 / Options

  • origin: Access-Control-Allow-Origin - default '*'
  • methods: Access-Control-Allow-Methods - default ['GET','POST','PUT','DELETE','PATCH','HEAD']
  • headers: Access-Control-Allow-Headers
  • credentials: Access-Control-Allow-Credentials - default false
  • secureContext: Cross-Origin-Opener/Embedder-Policy - default false
  • maxAge: Access-Control-Max-Age
  • privateNetworkAccess: Access-Control-Allow-Private-Network - default false

示例 / Examples

import { Controller, HttpMethod, Cors, Methods } from 'koa-use-decorator-router';

@Controller('/cors')
@Cors({
	origin: 'https://example.com',
	headers: ['Content-Type','Authorization'],
	methods: [Methods.GET, Methods.POST],
	credentials: true,
	maxAge: 3600,
	secureContext: true,
	privateNetworkAccess: false,
})
export class CorsController { ... }

@Controller('/cors')
@Cors('*', ['Content-Type','Authorization'], [Methods.GET, Methods.POST])
export class CorsController { ... }

@Controller('/cors')
@Cors()
export class CorsController { ... }
@Controller('/cors')
export class CorsController {
	@Cors({ origin: '*', headers: ['X-My-Header'], credentials: true })
	@HttpMethod.Get('/')
	index() {
		return 'ok';
	}

	@HttpMethod.Get('/index2')
	@Cors('*', ['Content-Type', 'Authorization'])
	index2() {
		return 'ok';
	}

	@HttpMethod.Get('/index3')
	@Cors()
	index3() {
		return 'ok';
	}
}
  • 条件装饰器 / Conditional Decorator

@IF 装饰器可以根据条件判断应用不同的装饰器,必须要链式调用 ENDIF 结束。

The @IF decorator allows applying different decorators based on a condition, and must be concluded by chaining a call to ENDIF.

import { Controller, HttpMethod, IF } from 'koa-use-decorator-router';

@(IF(process.env.SOME_ENV, Controller('/if-not'))
	.ELIF(process.env.SOME_ENV, Controller('/elif'))
	.ELSE(Controller('/else'))
	.ENDIF())
export class HomeController {
	@HttpMethod.Get('/')
	async index() {
		return 'Hello World!';
	}
}
  • 成员属性注入 / Property Injection

可以通过传递构造函数给 @Inject 装饰器来注入成员属性,或者在 ts 中通过类型反射来注入。如果使用的 ts 运行时环境不支持实时注入元数据(如 design:type),则一定需要在装饰器工厂函数中传入构造函数。

You can inject member properties by passing the class constructor to the @Inject decorator, or by using TypeScript's type reflection. If the TypeScript runtime environment does not support runtime injection of metadata (like design:type), you must pass the constructor function to the decorator factory function.

// HomeController.ts
import type { HomeService } from '@/service/HomeService';
import { Controller, HttpMethod, Inject } from 'koa-use-decorator-router';
import { HomeService2, HomeService3 } from '@/service/HomeService';

@Controller('/home')
export class HomeController {
	// 如果不用非空断言 (!),则 `tsconfig.json` 中必须关闭 `strictPropertyInitialization`
	// If you do not use the non-null assertion operator (!), you must disable `strictPropertyInitialization` in `tsconfig.json`.
	@Inject()
	service!: HomeService;

	@Inject(HomeService2)
	service2!: HomeService2;

	// 当将类的构造函数作为参数传入 `@Inject` 装饰器时,TypeScript 类型也可以使用 `any`
	// This means that when passing a class constructor to the `@Inject` decorator, the TypeScript type can be specified as `any`.
	@Inject(HomeService3)
	service3: any;

	@HttpMethod.Get('/')
	async index() {
		return this.service.show();
	}

	@HttpMethod.Get('/service2')
	async index2() {
		return this.service2.show();
	}

	@HttpMethod.Get('/service3')
	async index3() {
		return this.service3.show();
	}
}

// HomeService.ts
export class HomeService {
	show() {
		return 'Hello World!';
	}
}

export class HomeService2 {
	show() {
		return 'Hello World 2!';
	}
}

export class HomeService3 {
	show() {
		return 'Hello World 3!';
	}
}
  • 控制器基础路径覆盖 / Controller Base Path Override

import { Controller, HttpMethod, ControllerBasePathOverride } from 'koa-use-decorator-router';

@Controller('/home')
export class HomeController {
	@ControllerBasePathOverride()
	@HttpMethod.Get('/home2/:name')
	async string(@Inject('name') name: string) {
		return `Hello ${name}`;
	}
}
  • 路由实例方法代理 / Router Method Proxying

import Koa from 'koa';
import Decorator from 'koa-use-decorator-router';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const dir = resolve(dirname(fileURLToPath(import.meta.url)), './controller');

const app = new Koa();

const decorator = new Decorator(dir);
decorator.prefix('/api').param('user', (id, ctx, next) => {
	ctx.user = users[id];
	if (!ctx.user) return (ctx.status = 404);
	return next();
});
app.use(decorator.middleware()).use(decorator.allowedMethods());
  • 控制器文件过滤 / Controller File Filter

通过函数创建 / Create a custom filter function

app.use(
	decorator({
		controllerDir: dir,
		// 仅加载名为 IndexController 的文件(去除扩展名后比较)
		// Only load files with the name IndexController (without extension)
		matchFileName: 'IndexController',
	}),
);

app.use(
	decorator({
		controllerDir: dir,
		// 加载以 Admin 开头的控制器:AdminController、AdminUserController 等
		// Load controllers that start with Admin, such as AdminController, AdminUserController, etc.
		matchFileName: /^Admin/,
	}),
);

app.use(
	decorator({
		controllerDir: dir,
		matchFileName: ['IndexController', /^Admin/],
	}),
);

app.use(
	decorator({
		controllerDir: dir,
		// 自定义函数可以同时访问文件基名 (val) 和扩展名 (suffix)
		// Custom function can access both the file base name (val) and extension (suffix)
		matchFileName: (val, suffix) => {
			return val.startsWith('Index') && suffix === '.ts';
		},
	}),
);

通过构造函数创建 / Create a custom filter function

const decorator = new Decorator(dir);

// 链式添加规则(可以多次调用 matchFileName)
// Chainable rules (can be called multiple times)
decorator
	.matchFileName('IndexController')
	.matchFileName(/^Admin/)
	.matchFileName(['IndexController', /^Admin/]);

// 传递函数时会覆盖之前的所有规则
// Passing a function will override all previous rules
decorator.matchFileName((val, suffix) => val !== 'SkipController');
  • 控制器名称限制 / Controller Naming Convention

默认情况下,控制器文件名需要以 Controller 结尾。从 0.2.0 版本开始,可以通过配置关闭该限制。

By default, controller file names are expected to end with Controller. Starting from version 0.2.0, this restriction can be disabled via configuration.

app.use(
	decorator({
		controllerDir: dir,
		acceptAnyControllerName: true,
	}),
);

// or

app.use(decorator.acceptAnyControllerName().middleware());
  • 路由中间件 / Route Middleware (>= 1.1.0)

可以使用 RouteMiddleware 在类或方法层添加中间件,也可以通过 createMiddlewareDecorator 创建可复用的中间件装饰器。

You can use RouteMiddleware to add middleware at the class or method level, or you can create a middleware decorator factory using createMiddlewareDecorator.

import { Controller, HttpMethod, RouteMiddleware, createMiddlewareDecorator } from 'koa-use-decorator-router';

const Test = createMiddlewareDecorator((ctx, next) => {
	ctx.set('x-test', '1');
	return next();
});

@RouteMiddleware((ctx, next) => {
	ctx.set('x-class', '1');
	return next();
})
@Controller('/mw')
export class MiddlewareController {
	@HttpMethod.Get('/class')
	classRoute() {
		return 'ok';
	}

	@RouteMiddleware((ctx, next) => {
		ctx.set('x-method', '2');
		return next();
	})
	@HttpMethod.Get('/method')
	methodRoute() {
		return 'ok2';
	}

	@Test()
	@HttpMethod.Get('/test')
	testRoute() {
		return 'ok3';
	}
}

类级中间件会在方法级中间件之前执行,多个中间件按声明顺序合并。

Class-level middleware will execute before method-level middleware, and multiple middleware will be merged in order of declaration.

  • 命名路由 / Named Route (>= 1.1.0)

使用 NamedRoute('name') 为路由命名;初始化后的 Decorator 实例可以通过内部路由的 url 方法生成路径。

Use NamedRoute('name') to name routes; the initialized Decorator instance can generate paths using the internal router's url method.

import { Controller, HttpMethod, NamedRoute, Inject, Types } from 'koa-use-decorator-router';

@Controller('/named')
export class NamedRouteController {
	@NamedRoute('named.detail')
	@HttpMethod.Get('/detail/:id')
	detail(@Inject('id', Types.Int) id: number) {
		return id;
	}
}

生成 URL / Generate URL

const decorator = new Decorator(dir);
app.use(decorator.middleware());
const url = decorator.getRouter().url('named.detail', { id: '123' });
// => '/named/detail/123'