Skip to main content

在 NestJS 项目中,复用逻辑的放置位置 取决于这段处理的性质(纯工具函数还是包含业务逻辑/依赖)、使用范围(单个模块内还是多个模块),以及是否需要依赖注入(DI)。

下面按场景从简单到复杂给你推荐最佳实践(符合 NestJS 官方和社区主流做法):

1. 只在一个接口(或单个 Controller)中使用,逻辑比较简单

  • 推荐位置:直接在当前 Controller 的同目录下创建一个 helper 文件(或 utils 文件),写成普通纯函数。
    • 示例结构:
      src/users/
      ├── users.controller.ts
      ├── users.service.ts
      └── helpers/ # 或直接放 utils.ts
      └── process-data.helper.ts
    • process-data.helper.ts 示例:
      export function processSomeData(data: any): any {
      // 你的复用逻辑
      return transformedData;
      }
    • 在 Controller 中直接 import { processSomeData } from './helpers/process-data.helper'; 使用。

优点:简单、零开销,不需要注册为 Provider。
缺点:无法方便地注入其他 Service/Config 等。

2. 在同一个 Module 内多个 API(多个 Controller 方法)中使用

  • 推荐位置:创建一个 专用 Service(即使它不操作数据库,也符合 NestJS “业务逻辑放 Service” 的原则)。
    • 在当前 Module 的 providers 中注册它。
    • Controller 通过构造函数注入使用。

示例:

// src/users/process.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class ProcessService {
processSomeData(data: any) {
// 你的逻辑,可以在这里注入其他东西(如 ConfigService、Logger 等)
return ...;
}
}

然后在 users.module.ts

@Module({
providers: [UsersService, ProcessService], // 注册
controllers: [UsersController],
exports: [ProcessService], // 如果其他模块需要再导出
})
export class UsersModule {}

在 Controller 中注入:

constructor(
private readonly processService: ProcessService,
) {}

@Post()
async handle(@Body() body) {
const result = this.processService.processSomeData(body);
...
}

为什么用 Service 而不是普通函数?
NestJS 强调 薄 Controller + 厚 Service,Service 支持依赖注入、单例、测试更友好、AOP(拦截器等)支持更好。

3. 多个 API 使用,且跨越多个不同 Module(最常见场景)

  • 最佳实践:创建一个 Shared Module(共享模块),把复用逻辑封装成 Service 放在里面,然后让需要使用的 Module 去 import 它。

推荐目录结构(常见做法):

src/
├── common/ # 或 shared/
│ ├── common.module.ts
│ └── services/
│ └── process.service.ts # 或 utils.service.ts
├── users/
│ └── users.module.ts
├── orders/
│ └── orders.module.ts
└── app.module.ts

实现步骤

  1. 创建共享 Service(同上,@Injectable())。

  2. 创建 common.module.ts

    import { Module } from '@nestjs/common';
    import { ProcessService } from './services/process.service';

    @Module({
    providers: [ProcessService],
    exports: [ProcessService], // 必须导出,才能被其他模块使用
    })
    export class CommonModule {}
  3. 在需要使用的 Module 中导入:

    // users.module.ts
    @Module({
    imports: [CommonModule], // 导入共享模块
    controllers: [UsersController],
    providers: [UsersService],
    })
    export class UsersModule {}

    同样在 orders.module.ts 等也 imports: [CommonModule]

  4. 在任意 Controller/Service 中注入 ProcessService 使用。

额外好处

  • 避免循环依赖。
  • 代码复用性高,维护方便。
  • 如果共享的东西越来越多(utils、guards、interceptors、pipes 等),CommonModule 会越来越强大。

其他可选方式(根据场景选择)

  • 如果是纯无状态工具函数(不依赖任何 Service,不需要 DI):可以放在 src/common/utils/ 下,作为普通函数导出。多个地方直接 import 使用。简单但不够 “NestJS 风格”。
  • 如果是跨切面逻辑(如日志、数据转换、权限前置处理等):考虑用 Interceptor(拦截器)或 Middleware,全局或模块级注册,更优雅。
  • 如果是数据验证/转换:用 Pipe
  • 如果是领域特定逻辑:最好拆成独立的 Feature Module,而不是全扔到 common。

总结推荐优先级(从推荐到不推荐)

  1. Shared/Common Module + Service(跨模块复用,最推荐)
  2. 当前 Module 内的 Service(同模块多处使用)
  3. 普通 helper 函数(非常简单的纯函数)
  4. Interceptor / Guard / Pipe(如果是 AOP 性质的)

实际项目中,大部分团队都会建一个 common/shared/ 目录,专门放这些可复用部分。

如果你能告诉我这段处理的具体功能(比如是数据格式化、权限检查、计算逻辑、调用外部 API 等),我可以给你更精确的代码示例和目录建议。