模块的基本概念#
在Nest中,模块通过@Module装饰器来声明。每个应用都会有一个根模块,Nest框架会从根模块开始收集各个模块之间的依赖关系,形成依赖关系树。在应用初始化时,根据依赖关系树实例化不同的模块对象。如图:

在模块树中,每个模块都有自己独立的作用域,他们之间的代码是相互隔离的,各自拥有自己的控制器(Controllers)、服务提供者(Providers)、中间件(Middlerwares)和其他组件。
在我们新创建的工程中,比如只是使用命令创建一个新工程nest n nest-module -p pnpm,app.module.ts文件如下:
import { Module } from '@nestjs/common'import { AppController } from './app.controller'import { AppService } from './app.service'
@Module({ imports: [], controllers: [AppController], providers: [AppService],})export class AppModule {}其中AppModule是默认的根模块。类装饰器@Module的参数中,
controllers用于注入该模块的控制器集合
providers用于注入该模块的服务提供者(Service),这些Service在该模块中是共享的。
imports用于导入应用中的其他模块,这里默认是空的。
如果我们用cli命令生成其他的模块,比如User和Order模块
nest g res user --no-specnest g res order --no-spec这两个模块就会自动被引入到根模块,成为AppModule的子模块
@Module({ imports: [UserModule, OrderModule], controllers: [AppController], providers: [AppService],})export class AppModule {}共享模块#
模块和模块之间也是能引入的,假设存在这样的需求,Order模块需要依赖User模块中的UserService,这时,我们可以将UserService添加到UserModule的exports中,使它成为共享服务,这样,Order模块只需要导入UserModule即可访问到UserService
User模块中导出User服务:
@Module({ controllers: [UserController], providers: [UserService], // 导出 UserService,以便其他模块可以使用 exports: [UserService],})export class UserModule {}在Order模块中导入User模块
@Module({ // 导入 UserModule imports: [UserModule], controllers: [OrderController], providers: [OrderService],})export class OrderModule {}这样,在Order模块的任何地方,都可以共享UserService服务了,比如在order.service.ts中通过属性注入UserService依赖
@Injectable()export class OrderService { @Inject(UserService) private userService: UserService
// ......
findOne(id: number) { console.log('---' + this.userService.findOne(id) + '---') return `This action returns a #${id} order` }}全局模块#
如果某个模块在多个地方被引用,为了简化管理,可以使用@Global装饰器将其声明为全局模块。这样,只要在相关模块中,通过exports导出了,就相当于在全局已经注入,从而不需要在每个需要引入的模块中进行imports重复声明导入,比如,我们把User模块作为全局模块导出。
// 声明为全局模块@Global()@Module({ controllers: [UserController], providers: [UserService], exports: [UserService],})export class UserModule {}这样,如果我还想在Order模块中导入,就不再需要imports导入了。
@Module({ // 导入 UserModule // imports: [UserModule], controllers: [OrderController], providers: [OrderService],})export class OrderModule {}但是我们现在依然可以在Order模块中使用UserService,因为User模块已经在全局进行了注入。
动态模块#
前面介绍的都是静态模块的绑定和使用。Nest还提供了动态加载模块的功能,使得应用可以在运行时创建模块,通常用于动态读取配置或者根据权限判断来加载模块。
比如,我们还是像之前一样创建一个res,使用cli命令:nest g res auth --no-spec
这时,auth模块中的代码是这个样子的:
@Module({ controllers: [AuthController], providers: [AuthService],})export class AuthModule {}这是静态模块的样子,在AppModule中,通过import静态导入了:
@Module({ imports: [......, AuthModule], controllers: [AppController], providers: [AppService],})export class AppModule {}这样,每次 import 都是一样的,有的时候我们希望 import 的时候给这个模块传一些参数,动态生成模块的内容,这时候就需要 Dynamic Module 了
我们把AuthModule修改为动态模块
import { DynamicModule, Module } from '@nestjs/common'import { AuthService } from './auth.service'import { AuthController } from './auth.controller'
@Module({})export class AuthModule { static register(options: Record<string, any>): DynamicModule { return { module: AuthModule, controllers: [AuthController], providers: [ { provide: 'CONFIG_OPTIONS', useValue: options, }, AuthService, ], exports: [], } }}我们给AuthModule加一个 register 的静态方法,返回模块定义的对象,而且我们还可以把参数传入的 options 对象作为一个新的 provider。
现在在AppModule中引入,可以使用下面的方式:
@Module({ imports: [......, AuthModule.register({})], controllers: [AppController], providers: [AppService],})export class AppModule {}如果我们希望传入参数,比如在imports的时候传入:
@Module({ imports: [ ...... AuthModule.register({ role: 'admin', type: 'auth', }), ], controllers: [AppController], providers: [AppService],})export class AppModule {}在auth模块的controller中注入options
import { Controller, Get, Inject } from '@nestjs/common'import { AuthService } from './auth.service'
@Controller('auth')export class AuthController { constructor( private readonly authService: AuthService, @Inject('CONFIG_OPTIONS') private readonly options: Record<string, any> ) {}
@Get() findAll() { console.log(this.options) return this.authService.findAll() }}在findAll()方法中,我们就能获取到传入的options对象
这里的 register 方法其实并不是固定的,但 nest 约定了 3 种方法名:
- register
- forRoot
- forFeature
我们约定它们分别用来做不同的事情:
- register:用一次模块传一次配置,比如这次调用是
AuthModule.register({auth:’admin‘}),下一次可以AuthModule.register({auth:’guest‘}) - forRoot:配置一次模块用多次,通常用于注册根模块,例如配置根模块的全局服务或者中间件等等
- forFeature:用了 forRoot 固定了整体模块,用于局部的时候,可能需要再传一些配置,比如用 forRoot 指定了一些基础配置信息,再用 forFeature 指定某个模块访问的具体信息。具体我们后面使用
typeorm库的时候,大家一看就明白了。