NestJS 之模块(Module)
模块是 NestJS 应用结构的重要组成部分,通过 @Module(metadata) 装饰器定义,装饰器接收的对象属性描述了模块内的提供者、控制器、导入和导出内容
模块(Module)的作用
我们直接看 NestJS 的 @Module 的参数类型
@Module({
imports: [], // imports 用来导入其他模块
controllers: [UserController], // 注册控制器
providers: [UserService], // 注册提供者,供当前模块内使用
exports: [], // 导出外部需要的提供者(导入当前模块的时候 对外保留的 提供者
})
export class UserModule {}
从整体配置来看,模块的作用有:
- 组织代码,将相关的功能组织在一起
- 管理依赖,定义模块之间的导入导出关系
providers 和 exports 的区别
仅模块内部使用的提供者
如果一个服务只在当前模块的控制器 / 其他服务中使用,只需声明在 providers,无需声明在 exports 中
@Module({
controllers: [CatsController],
providers: [CatsService, CatsPrivateUtilService] // CatsPrivateUtilService 仅内部使用
})
export class CatsModule {}
// CatsPrivateUtilService 无法被外部模块访问,因为没有写入到 exports 配置里,因此实现私有逻辑隔离
需要跨模块共享的提供者
// 1. Cats 模块导出 CatsService
@Module({
providers: [CatsService],
exports: [CatsService]
})
export class CatsModule {}
// 2. Orders 模块导入 CatsModule,即可注入使用 CatsService
@Module({
imports: [CatsModule], // 导入后,CatsService 对 OrdersModule 可用
controllers: [OrdersController],
providers: [OrdersService]
})
export class OrdersModule {}
exports 和 providers 的注意事项:
- 直接导出未在 providers 声明的提供者. Nest 会报错,因为 exports 是对已有提供者的暴露,而非声明。
- 认为 exports 会重新实例化提供者. Nest 中模块默认是单例,exports 只是暴露已有实例的访问权限,不会为外部模块重新创建提供者实例,所有导入模块共享同一个实例
- 滥用 exports:导出所有提供者. 会破坏模块的封装性,仅导出外部模块真正需要的提供者即可。
根模块的作用
NestJS 中的根模块 AppModule 是整个应用的核心入口,作用为:
- 导入所有功能模块,即使用 imports 导入所有功能模块
- 管理全局配置、拦截拦截器、过滤器、连接数据库等功能
// AppModule 导入其他模块,形成模块树。
// TypeORM 实现了 Node.js 对数据库的 ORM 操作
// 而 TypeOrmModule 让 TypeORM 适配 NestJS 的模块化和 DI 机制
@Module({
imports: [UsersModule, CatsModule, TypeOrmModule.forRoot(DB_CONFIG)], // 子模块挂靠根模块
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
全局模块
import { Global, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Global()
@Module({
imports: [ConfigModule.forRoot()],
exports: [ConfigModule],
})
export class CustomConfigModule {}
全局模块是可以在任何其他模块中使用,且不需要在每个模块中导入
使用 @Global() 装饰器创建全局模块,全局模块的使用场景:
- 全局配置模块 如
ConfigModule, 在任何地方都可能用到 - 数据库模块配置,即全局性值的配置
动态模块(Dynamic Module)
支持运行时动态配置的模块,可通过静态方法(如forRoot/forFeature)接收参数,动态生成提供者、导入 / 导出内容,是 Nest 实现灵活配置的核心方式。
动态模块的特点
- 静态方法返回DynamicModule对象,包含 module/imports/providers/exports等元数据;
- 可接收自定义参数,适配不同场景的配置需求;
- 官方第三方模块(ConfigModule/TypeOrmModule)均实现了动态模块。
// FileUtilsModule(动态模块 demo)
@Module({})
export class FileUtilsModule {
// 静态方法接收配置,返回动态模块
static forRoot(config: FileConfig): DynamicModule {
return {
module: FileModule,
providers: [
{ provide: FILE_CONFIG, useValue: config }, // 注入配置
FileService,
],
exports: [FileService],
};
}
}
// 根模块传入配置初始化
@Module({
imports: [FileUtilsModule.forRoot({maxSize: '10MB', uploadPath: './uploads'})],
})
export class AppModule {}
共享模块(Shared Module)
提供通用能力、供多个模块复用的模块,核心价值是解决代码重复问题,实现跨模块依赖复用, 加密、时间处理工具类 在订单和账务模块都需要用到时可以将两者结合成一个共享模块
// UtilsModule(共享模块)
@Module({
providers: [EncryptService, TimeService],
exports: [EncryptService, TimeService], // 暴露通用工具服务
})
export class UtilsModule {}
// 其他模块导入即可复用
@Module({
imports: [UtilsModule],
providers: [OrderService],
})
export class OrderModule {}
异步模块(Async Module)
动态模块的异步版本,通过 forRootAsync/forFeatureAsync 实现异步配置加载,解决 “配置依赖前置” 问题(如从配置服务、数据库、远程接口读取配
异步模块的特点
- 动态模块实现,返回Promise;
- 支持通过
@Inject注入其他提供者(如ConfigService),异步获取配置; - 开发中高频使用,是优雅管理配置的标准方式
// 根模块中异步初始化TypeORM(从ConfigService读取配置)
@Module({
imports: [
TypeOrmModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('DB_HOST'),
port: configService.get<number>('DB_PORT'),
database: configService.get('DB_DATABASE'),
autoLoadEntities: true,
synchronize: false,
}),
inject: [ConfigService], // 注入ConfigService,异步获取配置
}),
],
})
export class AppModule {}
各类模块的使用原则
- 优先使用特性模块(比如 Order, User 模块) + 共享模块
- 全局模块仅用于高频通用能力:避免所有模块都设为全局,否则会导致依赖关系混乱;
- 动态 / 异步模块用于带配置的初始化:官方第三方模块优先用其提供的forRoot/forRootAsync;
- 按需导出:无论哪种模块,仅导出外部模块真正需要的提供者,避免过度暴露。
- 建议按功能组织,比如 modules/user 放所有有关 user 模块、控制器(Controller)、服务(Service), modules/order 放 order 相关的模块