在 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 通过构造函数注入使用。
- 在当前 Module 的
示例:
// 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
实现步骤:
-
创建共享 Service(同上,
@Injectable())。 -
创建
common.module.ts:import { Module } from '@nestjs/common';
import { ProcessService } from './services/process.service';
@Module({
providers: [ProcessService],
exports: [ProcessService], // 必须导出,才能被其他模块使用
})
export class CommonModule {} -
在需要使用的 Module 中导入:
// users.module.ts
@Module({
imports: [CommonModule], // 导入共享模块
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}同样在
orders.module.ts等也imports: [CommonModule]。 -
在任意 Controller/Service 中注入
ProcessService使用。
额外好处:
- 避免循环依赖。
- 代码复用性高,维护方便。
- 如果共享的东西越来越多(utils、guards、interceptors、pipes 等),CommonModule 会越来越强大。