Skip to content

模块的基本概念

模块的基本概念#

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

image-20250124154209700

在模块树中,每个模块都有自己独立的作用域,他们之间的代码是相互隔离的,各自拥有自己的控制器(Controllers)、服务提供者(Providers)、中间件(Middlerwares)和其他组件。

在我们新创建的工程中,比如只是使用命令创建一个新工程nest n nest-module -p pnpmapp.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-spec
nest 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 种方法名:

我们约定它们分别用来做不同的事情: