You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何手动实现TypeScript HTTP装饰器?基于Node.js+Express技术栈

自行实现NestJS风格的Express装饰器

1. 配置TypeScript编译选项

首先在tsconfig.json中开启装饰器相关编译开关,这是使用TypeScript装饰器的基础:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2020",
    "module": "CommonJS",
    "strict": true,
    "esModuleInterop": true
  }
}

安装reflect-metadata依赖(TypeScript装饰器的元数据读取必须依赖它):

npm install reflect-metadata --save

在项目入口文件顶部引入该库:

import 'reflect-metadata';

2. 定义元数据常量

用Symbol作为元数据的键,避免命名冲突:

export const CONTROLLER_METADATA = Symbol('controller');
export const ROUTE_METADATA = Symbol('route');
export const PARAMS_METADATA = Symbol('params');
export const BODY_METADATA = Symbol('body');

3. 实现@Controller装饰器

标记类为控制器,同时存储路由前缀:

import { CONTROLLER_METADATA } from './metadata';

export function Controller(prefix: string = ''): ClassDecorator {
  return (target) => {
    Reflect.defineMetadata(CONTROLLER_METADATA, prefix, target);
  };
}

4. 实现HTTP方法装饰器(@Get、@Post等)

通过工厂函数生成不同HTTP方法的装饰器,减少重复代码:

import { ROUTE_METADATA } from './metadata';

type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';

function createHttpMethodDecorator(method: HttpMethod) {
  return (path: string = ''): MethodDecorator => {
    return (target, propertyKey) => {
      const existingRoutes = Reflect.getMetadata(ROUTE_METADATA, target.constructor) || [];
      existingRoutes.push({
        method,
        path,
        handlerName: propertyKey as string
      });
      Reflect.defineMetadata(ROUTE_METADATA, existingRoutes, target.constructor);
    };
  };
}

export const Get = createHttpMethodDecorator('get');
export const Post = createHttpMethodDecorator('post');
export const Put = createHttpMethodDecorator('put');
export const Patch = createHttpMethodDecorator('patch');
export const Delete = createHttpMethodDecorator('delete');

5. 实现参数装饰器(@Params、@Body)

记录参数的类型与索引位置,后续从Express的req对象中提取对应数据:

import { PARAMS_METADATA, BODY_METADATA } from './metadata';

export function Params(): ParameterDecorator {
  return (target, propertyKey, parameterIndex) => {
    const paramsMetadata = Reflect.getMetadata(PARAMS_METADATA, target, propertyKey) || [];
    paramsMetadata.push(parameterIndex);
    Reflect.defineMetadata(PARAMS_METADATA, paramsMetadata, target, propertyKey);
  };
}

export function Body(): ParameterDecorator {
  return (target, propertyKey, parameterIndex) => {
    const bodyMetadata = Reflect.getMetadata(BODY_METADATA, target, propertyKey) || [];
    bodyMetadata.push(parameterIndex);
    Reflect.defineMetadata(BODY_METADATA, bodyMetadata, target, propertyKey);
  };
}

6. 实现路由扫描与Express路由器生成

编写函数扫描控制器类,读取元数据并生成对应Express路由:

import express, { Router, Request, Response } from 'express';
import { CONTROLLER_METADATA, ROUTE_METADATA, PARAMS_METADATA, BODY_METADATA } from './metadata';

export function createRouter(controllers: any[]): Router {
  const router = express.Router();

  controllers.forEach(ControllerClass => {
    const controllerPrefix = Reflect.getMetadata(CONTROLLER_METADATA, ControllerClass);
    const controllerInstance = new ControllerClass();
    const routes = Reflect.getMetadata(ROUTE_METADATA, ControllerClass) || [];

    routes.forEach(route => {
      const { method, path, handlerName } = route;
      const handler = controllerInstance[handlerName] as Function;

      // 读取参数注入元数据
      const paramsIndices = Reflect.getMetadata(PARAMS_METADATA, controllerInstance, handlerName) || [];
      const bodyIndices = Reflect.getMetadata(BODY_METADATA, controllerInstance, handlerName) || [];

      router[method](`${controllerPrefix}${path}`, (req: Request, res: Response) => {
        const args: any[] = [];

        // 注入@Params参数
        paramsIndices.forEach(index => {
          args[index] = req.params;
          // 若需支持@Params('id')单参数提取,可修改Params装饰器存储参数名,此处为通用版本
        });

        // 注入@Body参数
        bodyIndices.forEach(index => {
          args[index] = req.body;
        });

        // 执行处理函数并响应结果
        const result = handler.apply(controllerInstance, args);
        if (result instanceof Promise) {
          result.then(data => res.send(data)).catch(err => res.status(500).send(err));
        } else {
          res.send(result);
        }
      });
    });
  });

  return router;
}

7. 使用示例

编写控制器

import { Controller, Get, Post, Params, Body } from './decorators';

@Controller('/users')
export class UserController {
  @Get('/:id')
  findOne(@Params() params: { id: string }) {
    return { userId: params.id, name: 'Test User' };
  }

  @Post('/')
  create(@Body() userData: { name: string, email: string }) {
    return { id: '123', ...userData };
  }
}

启动Express服务器

import express from 'express';
import 'reflect-metadata';
import { createRouter } from './router-creator';
import { UserController } from './user.controller';

const app = express();
app.use(express.json());

const router = createRouter([UserController]);
app.use(router);

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

补充说明

  • 若需支持@Params('id')单参数提取,可修改Params装饰器接收参数名,在路由处理函数中提取对应值。
  • 可扩展实现@Query()、@Headers()等装饰器,原理与@Params、@Body一致。
  • 可添加全局异常处理逻辑,优化错误响应。

内容的提问来源于stack exchange,提问作者Sabgramar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 09:30:49