模块
模块是一个用 @Module() 装饰器注解的类。这个装饰器提供了 Nest 用来高效组织和管理应用结构的元数据。

每个 Nest 应用至少有一个模块,即根模块,它作为 Nest 构建应用图的起点。这个图是 Nest 用来解析模块和提供者之间关系和依赖的内部结构。虽然小型应用可能只有一个根模块,但通常情况并非如此。模块被强烈推荐作为组织组件的有效方式。对于大多数应用,你可能会有多个模块,每个模块封装一组密切相关的功能。
@Module() 装饰器接受一个对象,其属性描述了模块:
providers | 将由 Nest 注入器实例化的提供者,至少可以在此模块内共享 |
controllers | 此模块中定义的需要实例化的控制器集合 |
imports | 导入的模块列表,这些模块导出了此模块所需的提供者 |
exports | 此模块提供的 providers 子集,应在导入此模块的其他模块中可用。你可以使用提供者本身或仅使用其令牌(provide 值) |
模块默认封装提供者,这意味着你只能注入属于当前模块的提供者或从其他导入模块显式导出的提供者。模块导出的提供者本质上是模块的公共接口或 API。
功能模块
在我们的示例中,CatsController 和 CatsService 密切相关,服务于同一应用领域。将它们分组到一个功能模块中是有意义的。功能模块组织与特定功能相关的代码,帮助维护清晰的边界和更好的组织。随着应用或团队的增长,这一点尤为重要,并且符合 SOLID 原则。
接下来,我们将创建 CatsModule 来演示如何将控制器和服务分组。
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}提示
要使用 CLI 创建模块,只需执行 $ nest g module cats 命令。
上面,我们在 cats.module.ts 文件中定义了 CatsModule,并将与此模块相关的所有内容移到了 cats 目录中。我们需要做的最后一件事是将此模块导入到根模块(AppModule,定义在 app.module.ts 文件中)。
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule {}现在我们的目录结构如下所示:
src
├── cats
│ ├── dto
│ │ └── create-cat.dto.ts
│ ├── interfaces
│ │ └── cat.interface.ts
│ ├── cats.controller.ts
│ ├── cats.module.ts
│ └── cats.service.ts
├── app.module.ts
└── main.ts共享模块
在 Nest 中,模块默认是单例的,因此你可以轻松地在多个模块之间共享任何提供者的同一实例。

每个模块自动成为共享模块。一旦创建,它可以被任何模块重用。假设我们想在多个其他模块之间共享 CatsService 的实例。为此,我们首先需要通过将 CatsService 提供者添加到模块的 exports 数组来导出它,如下所示:
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService]
})
export class CatsModule {}现在,任何导入 CatsModule 的模块都可以访问 CatsService,并将与所有其他导入它的模块共享同一实例。
模块重新导出
如上所示,模块可以导出其内部提供者。此外,它们还可以重新导出它们导入的模块。在下面的示例中,CommonModule 既被导入到 CoreModule 中,又从 CoreModule 导出,使其可供导入此模块的其他模块使用。
@Module({
imports: [CommonModule],
exports: [CommonModule],
})
export class CoreModule {}依赖注入
模块类也可以注入提供者(例如,用于配置目的):
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {
constructor(private catsService: CatsService) {}
}然而,由于循环依赖的原因,模块类本身不能作为提供者被注入。
全局模块
如果你必须在所有地方导入相同的模块集,这会变得很繁琐。与 Nest 不同,Angular 的 providers 注册在全局作用域中。一旦定义,它们在任何地方都可用。然而,Nest 将提供者封装在模块作用域内。如果不先导入封装模块,你就无法在其他地方使用模块的提供者。
当你想提供一组应该开箱即用地在任何地方可用的提供者(例如,辅助工具、数据库连接等)时,使用 @Global() 装饰器使模块成为全局模块。
import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Global()
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}@Global() 装饰器使模块成为全局作用域。全局模块应该只注册一次,通常由根模块或核心模块注册。在上面的示例中,CatsService 提供者将无处不在,希望注入该服务的模块不需要在其 imports 数组中导入 CatsModule。
提示
将所有东西都设为全局并不是推荐的设计实践。虽然全局模块可以帮助减少样板代码,但通常最好使用 imports 数组以受控和清晰的方式使模块的 API 对其他模块可用。
动态模块
Nest 中的动态模块允许你创建可以在运行时配置的模块。当你需要提供灵活、可定制的模块,其中提供者可以基于某些选项或配置创建时,这特别有用。以下是动态模块工作原理的简要概述。
import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';
@Module({
providers: [Connection],
exports: [Connection],
})
export class DatabaseModule {
static forRoot(entities = [], options?): DynamicModule {
const providers = createDatabaseProviders(options, entities);
return {
module: DatabaseModule,
providers: providers,
exports: providers,
};
}
}提示
forRoot() 方法可以同步或异步(即通过 Promise)返回动态模块。
此模块默认定义了 Connection 提供者(在 @Module() 装饰器元数据中),但另外——根据传入 forRoot() 方法的 entities 和 options 对象——暴露了一组提供者,例如仓库。请注意,动态模块返回的属性扩展(而不是覆盖)@Module() 装饰器中定义的基础模块元数据。这就是静态声明的 Connection 提供者和动态生成的仓库提供者都从模块导出的方式。
如果你想在全局作用域中注册动态模块,请将 global 属性设置为 true。
{
global: true,
module: DatabaseModule,
providers: providers,
exports: providers,
}警告
如上所述,将所有东西都设为全局不是一个好的设计决策。
DatabaseModule 可以按以下方式导入和配置:
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';
@Module({
imports: [DatabaseModule.forRoot([User])],
})
export class AppModule {}如果你想反过来重新导出动态模块,可以在 exports 数组中省略 forRoot() 方法调用:
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';
@Module({
imports: [DatabaseModule.forRoot([User])],
exports: [DatabaseModule],
})
export class AppModule {}提示
在这个章节中了解如何使用 ConfigurableModuleBuilder 构建高度可定制的动态模块。