动态模块
本教程共 47 篇 · 第 10 篇 · 更新于 2026-08-09 · 约 10 分钟阅读
本节目标:理解动态模块的用途,学会用
forRoot/register模式创建可配置的模块。
静态模块的局限
前面学的模块都是”静态”的——在 @Module() 装饰器里写死了 imports、providers、exports:
@Module({
providers: [ConfigService],
exports: [ConfigService],
})
export class ConfigModule {}
这种方式有个问题:模块的使用者没法影响模块的行为。
比如你写了一个 ConfigModule,想让别人能指定 .env 文件的路径。静态模块做不到——配置在编译时就写死了。
动态模块是什么
动态模块就是”运行时创建的模块”。它不是用 @Module() 装饰器定义的,而是用一个静态方法返回的。
使用时的区别:
// 静态导入——没法配置
@Module({
imports: [ConfigModule],
})
export class AppModule {}
// 动态导入——可以传配置
@Module({
imports: [ConfigModule.register({ folder: './config' })],
})
export class AppModule {}
ConfigModule.register() 返回一个 DynamicModule 对象。你在调用时可以传入配置参数,模块会根据这些参数动态生成 providers。
打个比方:静态模块像买成衣,尺码固定。动态模块像定制西装,你告诉裁缝你的尺寸,他给你量身定做。
DynamicModule 的结构
动态模块返回的对象和普通模块的 @Module() 配置几乎一样,多了一个 module 属性:
import { DynamicModule, Module } from '@nestjs/common';
import { ConfigService } from './config.service';
@Module({})
export class ConfigModule {
static register(options: ConfigOptions): DynamicModule {
return {
module: ConfigModule, // 必须有,指向模块类本身
providers: [
{
provide: 'CONFIG_OPTIONS',
useValue: options, // 把传入的配置注册为 Provider
},
ConfigService,
],
exports: [ConfigService],
};
}
}
module: ConfigModule 是必须的——告诉 NestJS 这是哪个模块。其他属性(providers、imports、exports、controllers)和静态模块一样。
Note除了
module属性是必须的,其他属性都可以省略。你可以返回一个只有module和providers的动态模块。
配置怎么传进去的
整个流程分三步:
第一步:使用方调用 register() 传入配置。
ConfigModule.register({ folder: './config' })
第二步:register() 把配置对象注册为一个 Provider。
providers: [
{
provide: 'CONFIG_OPTIONS',
useValue: options, // 配置对象变成 Provider 的值
},
ConfigService,
]
第三步:ConfigService 通过 @Inject() 拿到配置。
@Injectable()
export class ConfigService {
constructor(
@Inject('CONFIG_OPTIONS') private options: ConfigOptions,
) {
// 现在可以用 options.folder 了
const filePath = path.resolve(options.folder, '.env');
this.envConfig = dotenv.parse(fs.readFileSync(filePath));
}
get(key: string): string {
return this.envConfig[key];
}
}
Tip核心思路:把传入的配置选项变成一个 Provider,然后通过依赖注入传给 Service。这就是 NestJS 处理动态配置的标准模式。
forRoot / register / forFeature
你可能见过不同的方法名:forRoot、register、forFeature。它们没有技术上的区别,只是社区约定:
| 方法名 | 用途 | 使用次数 |
|---|---|---|
forRoot() | 全局配置,通常在根模块调用一次 | 一次 |
register() | 注册一个可配置的模块 | 多次 |
forFeature() | 在 forRoot 基础上,为特定模块做额外配置 | 多次 |
举个例子:
// 数据库配置——全局一次
@Module({
imports: [TypeOrmModule.forRoot({ type: 'mysql', ... })],
})
export class AppModule {}
// 用户模块——注册特定功能
@Module({
imports: [HttpModule.register({ baseUrl: 'https://api.example.com' })],
})
export class UsersModule {}
// 特定模块的功能配置
@Module({
imports: [TypeOrmModule.forFeature([User])],
})
export class UsersModule {}
每个方法都有对应的异步版本:forRootAsync、registerAsync、forFeatureAsync。
异步配置
有时候配置本身也需要异步获取——比如从远程服务拉取配置。这时候用异步版本:
@Module({
imports: [
ConfigModule.registerAsync({
useFactory: async () => {
const response = await fetch('https://config-service/api/config');
return await response.json();
},
}),
],
})
export class AppModule {}
也可以注入其他 Provider 来获取配置:
@Module({
imports: [
ConfigModule.registerAsync({
imports: [EnvModule],
useFactory: (envService: EnvService) => {
return {
folder: envService.getConfigFolder(),
};
},
inject: [EnvService],
}),
],
})
export class AppModule {}
全局动态模块
动态模块也可以设置 global: true 变成全局模块:
static register(options: ConfigOptions): DynamicModule {
return {
module: ConfigModule,
global: true, // 全局可用
providers: [
{
provide: 'CONFIG_OPTIONS',
useValue: options,
},
ConfigService,
],
exports: [ConfigService],
};
}
Tip
@nestjs/config的ConfigModule.forRoot({ isGlobal: true })就是这么干的。
ConfigurableModuleBuilder
手动写 register 和 registerAsync 挺繁琐的。NestJS 11 提供了 ConfigurableModuleBuilder 来简化这个过程。
第一步:定义配置接口。
export interface ConfigModuleOptions {
folder: string;
}
第二步:创建模块定义文件。
// config.module-definition.ts
import { ConfigurableModuleBuilder } from '@nestjs/common';
export const {
ConfigurableModuleClass,
MODULE_OPTIONS_TOKEN,
} = new ConfigurableModuleBuilder<ConfigModuleOptions>().build();
第三步:让模块继承生成的基类。
import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';
import {
ConfigurableModuleClass,
MODULE_OPTIONS_TOKEN,
} from './config.module-definition';
@Module({
providers: [ConfigService],
exports: [ConfigService],
})
export class ConfigModule extends ConfigurableModuleClass {}
就这样,ConfigModule 自动获得了 register() 和 registerAsync() 两个方法。
第四步:在 Service 里注入配置。
@Injectable()
export class ConfigService {
constructor(
@Inject(MODULE_OPTIONS_TOKEN) private options: ConfigModuleOptions,
) {}
}
使用:
@Module({
imports: [
ConfigModule.register({ folder: './config' }),
// 或者异步版本:
// ConfigModule.registerAsync({
// useFactory: () => ({ folder: './config' }),
// }),
],
})
export class AppModule {}
Note
ConfigurableModuleBuilder默认生成register和registerAsync。如果你想用forRoot,可以这样配置:new ConfigurableModuleBuilder<ConfigModuleOptions>() .setClassMethodName('forRoot') .build();
额外选项
有些选项(比如 isGlobal)不应该出现在 MODULE_OPTIONS_TOKEN 里——Service 不需要知道模块是不是全局的。用 setExtras 处理:
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<ConfigModuleOptions>()
.setExtras(
{ isGlobal: true }, // 默认值
(definition, extras) => ({
...definition,
global: extras.isGlobal, // 映射到 DynamicModule 的 global 属性
}),
)
.build();
使用时 isGlobal 不会出现在注入的 options 里:
ConfigModule.register({
isGlobal: true, // 控制模块是否全局
folder: './config', // 传给 Service 的配置
})
实际案例:数据库模块
import { Module, DynamicModule } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
interface DatabaseOptions {
type: 'mysql' | 'postgres';
host: string;
port: number;
database: string;
entities: any[];
synchronize: boolean;
}
@Module({})
export class DatabaseModule {
static forRoot(options: DatabaseOptions): DynamicModule {
return {
module: DatabaseModule,
imports: [
TypeOrmModule.forRoot({
type: options.type,
host: options.host,
port: options.port,
database: options.database,
entities: options.entities,
synchronize: options.synchronize,
}),
],
exports: [TypeOrmModule],
};
}
static forFeature(entities: any[]): DynamicModule {
return {
module: DatabaseModule,
imports: [TypeOrmModule.forFeature(entities)],
exports: [TypeOrmModule],
};
}
}
使用:
@Module({
imports: [
DatabaseModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
database: 'mydb',
entities: [User, Product],
synchronize: true,
}),
],
})
export class AppModule {}
小结
动态模块让你的模块变成”可配置的插件”。
核心要点:
- 动态模块就是一个返回
DynamicModule对象的静态方法 module属性必须指向模块类本身- 把配置选项注册为 Provider,通过 DI 传给 Service
forRoot全局配置一次,forFeature模块级别配置ConfigurableModuleBuilder自动生成register和registerAsync,减少样板代码
你日常开发中用 @nestjs/typeorm、@nestjs/config 这些包时,用的 forRoot() 就是动态模块。现在你知道它底层是怎么回事了。