每个应用程序至少有一个模块,即根模块。根模块是 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
目录中。我们需要做的最后一件事是将此模块导入根模块(在 app.module.ts
文件中定义的 AppModule
)。
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 {}
依赖注入
模块类也可以注入提供程序(例如,出于配置目的):
// cats.module
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 { Global, Module } 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
提供程序将无处不在,希望注入服务的模块无需在其导入数组中导入 CatsModule
。
将所有内容都设置为全局不是一个好的设计决策。全局模块可用于减少必要的样板文件数量。imports
数组通常是使模块的 API 可供消费者使用的首选方式。
动态模块
Nest 模块系统包含一个称为 动态模块 的强大功能。此功能使您能够轻松创建可自定义的模块,这些模块可以动态注册和配置提供程序。此处 广泛介绍了动态模块。在本章中,我们将简要概述以完成对模块的介绍。
以下是DatabaseModule
动态模块定义的示例:
import { DynamicModule, Module } 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,
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 {}
如果你想要依次重新导出动态模块,则可以省略导出数组中的 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
构建高度可定制的动态模块。