在 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 会越来越强大。
其他可选方式(根据场景选择)
- 如果是纯无状态工具函数(不依赖任何 Service,不需要 DI):可以放在
src/common/utils/下,作为普通函数导出。多个地方直接 import 使用。简单但不够 “NestJS 风格”。 - 如果是跨切面逻辑(如日志、数据转换、权限前置处理等):考虑用 Interceptor(拦截器)或 Middleware,全局或模块级注册,更优雅。
- 如果是数据验证/转换:用 Pipe。
- 如果是领域特定逻辑:最好拆成独立的 Feature Module,而不是全扔到 common。
总结推荐优先级(从推荐到不推荐)
- Shared/Common Module + Service(跨模块复用,最推荐)
- 当前 Module 内的 Service(同模块多处使用)
- 普通 helper 函数(非常简单的纯函数)
- Interceptor / Guard / Pipe(如果是 AOP 性质的)
实际项目中,大部分团队都会建一个 common/ 或 shared/ 目录,专门放这些可复用部分。
如果你能告诉我这段处理的具体功能(比如是数据格式化、权限检查、计算逻辑、调用外部 API 等),我可以给你更精确的代码示例和目录建议。