跳到主要内容

创建日期:2026-09-08 | 最近更新:2026-09-08 基于 NestJS 12@nestjs/core 12.0.1)。本文的「模块接线 / 依赖注入」在本机跑通(见下方路由日志),作用域与生命周期属框架级概念,写法以官方 modules / providers 文档为准。

NestJS 模块化与 Provider 作用域:把应用拆成可复用的「域」

一句话:Nest 的 @Module 不是装饰器仪式,它是依赖注入的边界——「谁 import 谁、谁 exports 谁」决定了容器里谁能注入谁。掌握 Module 的 imports/exports/providers/controllers 四要素和 Provider 的三种作用域,你才算真正会组织 Nest 工程。

1. 回顾:Module 就是「一个可复用的依赖域」

@Module({
imports: [], // 引入别的模块 → 本模块能注入对方 exports 的 provider
controllers: [CatsController], // 本模块的路由
providers: [CatsService], // 本模块的 provider(可注入)
exports: [CatsService], // 允许「imports 我的模块」也注入 CatsService
})
export class CatsModule {}

四要素一句话版:

字段意思
controllers这个域里谁接收 HTTP
providers这个域里谁干活(可被注入)
imports我要用哪些「别的域」
exports我允许谁把「我的 provider」拿出去用

默认规则:provider 只在声明它的模块内可见。 想让 A 模块里的 XxxServiceB 使用,得 A exports 它 + B imports A。这不是麻烦,是刻意做封装——大项目靠这个保持「依赖边界清晰」。

上一篇(入门)里那个 cats demo 就是这样:AppModule imports CatsModule,启动日志能直接看到 Mapped {/cats, POST} route——一个特性模块接进去,路由就有了。

2. 组织习惯:一个业务域一个 Module

src/
├─ app.module.ts # 根模块:只做「组装 + 全局」的薄壳
├─ cats/
│ ├─ cats.module.ts # 业务域
│ ├─ cats.controller.ts
│ ├─ cats.service.ts
│ └─ dto/create-cat.dto.ts
├─ common/ # 跨域共享(guard/filters/interceptors/decorators)
└─ config/ # 全局配置模块
  • 根模块尽量薄:只 import 特性模块、注册全局管道/过滤器/守卫;
  • 一个文件一个类:controller / service / dto / entity 分开,别堆;
  • DTO/接口靠近它所属的域,而不是放一个中央 types/ 大杂烩。

3. 动态模块:为什么配置、DB 的 Module 都长得像工厂

你天天 TypeOrmModule.forRoot(...)ConfigModule.forRoot(...)——forRoot 就是「动态模块」:模块不是写死的,而是根据你传的选项「生成一个带不同 providers 的模块实例」。

// 极简动态模块:调用方能自定义 token
@Module({})
export class CacheModule {
static forRoot(ttl: number): DynamicModule {
return {
module: CacheModule,
providers: [
{ provide: 'CACHE_TTL', useValue: ttl },
CacheService,
],
exports: [CacheService],
};
}
}
// 使用方:CacheModule.forRoot(3600)

看到价值了:同一套模块逻辑,按不同配置产出不同实例——ConfigModule 读不同 env、TypeOrmModule 连不同库,都是它。

@Global:真要全局共享时才用

默认封装严格,但有些 provider(日志、配置、全局工具)谁都要用,@Global() 让模块只注册一次、处处可注入(不用到处 import):

@Global()
@Module({ providers: [LoggerService], exports: [LoggerService] })
export class CommonModule {}

克制使用:@Global 用多了等于把「封装」又拆了。经验法则——只对「真·基础设施」全局化,业务 provider 老老实实走 imports/exports。

4. Provider 三种作用域

默认 provider 是单例(整个应用一份)。还可以:

import { Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.DEFAULT }) // 单例(默认)
export class DefaultSvc {}

@Injectable({ scope: Scope.REQUEST }) // 每个请求一份(可读请求上下文)
export class RequestSvc {}

@Injectable({ scope: Scope.TRANSIENT }) // 每次注入都 new 一份(不共享)
export class TransientSvc {}
scope生命周期典型用途代价
DEFAULT应用级单例绝大多数 service
REQUEST每个 HTTP 请求想读 req.user、按请求隔离状态注入链上所有依赖都要 REQUEST,性能/测试变复杂
TRANSIENT每次注入各一份有内部状态、不想要单例共享的类每次实例化开销

REQUEST 作用域的坑:它要求整条依赖链也都是 REQUEST(REQ → A → B → C 全要标),否则会报错或拿到错误实例。能用构造器参数拿不到请求时,优先考虑把「请求相关数据」作为方法参数传入,而不是把 service 设成 REQUEST。

5. 生命周期钩子:在「启动/关闭」时干点正事

provider / controller / module 都能实现以下接口,容器在对应时机调用:

import { OnModuleInit, Injectable, Logger } from '@nestjs/common';

@Injectable()
export class AppService implements OnModuleInit {
private readonly logger = new Logger(AppService.name);
onModuleInit() {
this.logger.log('服务初始化:这里适合连 DB、预热缓存');
}
}
接口时机
OnModuleInit该模块的依赖解析完后
OnApplicationBootstrap所有模块都 init 完、应用将监听前
OnModuleDestroy / BeforeApplicationShutdown / OnApplicationShutdown关闭阶段(优雅停机、断连)

要在关闭时收到系统信号(SIGINT/SIGTERM),在 main.ts 开:

app.enableShutdownHooks(); // 配合容器/PM2 发信号做优雅停机

这正是连接 DB/Redis 这类「有连接生命周期」资源的正确位置——比在 controller 里偷偷建连接靠谱得多。

6. 自定义 Provider:别只会写 providers: [X]

providers: [X]{ provide: X, useClass: X } 的简写。拆开能表达更多:

@Module({
providers: [
{ provide: 'CONFIG', useValue: { env: 'prod' } }, // 值(用字符串/符号 token)
{ provide: 'DB', useFactory: (cfg) => createDb(cfg), // 工厂(惰性 + 可注入)
inject: ['CONFIG'] },
{ provide: CatsService, useClass: MockCatsService }, // 换实现(测试常用)
],
})

注入方:

constructor(
@Inject('CONFIG') private readonly config: { env: string },
) {}

自定义 token + useFactory 是「对接第三方库」的标准姿势(把非 Nest 的对象包装成可注入 provider);useClass 换实现是单测里 mock 服务的利器。

7. 小结

  • 写模块:先问「这个 provider 该谁用」——决定了放哪个 Module、要不要 exports;
  • 拆域:按业务域建 Module,根模块做薄壳;
  • 要配置化:动态模块 forRoot/forFeature;要全局基础设施:才 @Global
  • 默认单例,别没事开 REQUEST;真要就整条链一致;
  • 资源生命周期(连库、连 Redis、优雅停机)交给 OnModuleInit / enableShutdownHooks

关联