NestJS 模块作用、类型

36 阅读5分钟

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 {}

exportsproviders 的注意事项:

  • 直接导出未在 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 相关的模块