Що таке динамічні модулі в NestJS і коли їх використовувати?
Динамічні модулі в NestJS
Динамічні модулі дозволяють створювати конфігуровані, повторно використовувані модулі, які приймають параметри під час імпорту. Саме так працюють бібліотеки, такі як @nestjs/jwt, @nestjs/bull та TypeOrmModule.
Статичні та динамічні модулі
Статичний модуль
typescript
@Module({
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
// Використання — без конфігурації
@Module({
imports: [CatsModule],
})
export class AppModule {}Динамічний модуль
typescript
// Використання — з конфігурацією
@Module({
imports: [
DatabaseModule.forRoot({ host: 'localhost', port: 5432 }),
],
})
export class AppModule {}Створення динамічного модуля
Основний шаблон: forRoot / forFeature
typescript
export interface DatabaseModuleOptions {
host: string;
port: number;
username: string;
password: string;
database: string;
}
@Module({})
export class DatabaseModule {
static forRoot(options: DatabaseModuleOptions): DynamicModule {
return {
module: DatabaseModule,
global: true, // Доступний скрізь
providers: [
{
provide: 'DATABASE_OPTIONS',
useValue: options,
},
{
provide: 'DATABASE_CONNECTION',
useFactory: async (opts: DatabaseModuleOptions) => {
return createConnection(opts);
},
inject: ['DATABASE_OPTIONS'],
},
DatabaseService,
],
exports: ['DATABASE_CONNECTION', DatabaseService],
};
}
}Асинхронна конфігурація (forRootAsync)
Для випадків, коли параметри надходять з середовища або інших сервісів:
typescript
@Module({})
export class DatabaseModule {
static forRoot(options: DatabaseModuleOptions): DynamicModule {
return {
module: DatabaseModule,
providers: [
{ provide: 'DATABASE_OPTIONS', useValue: options },
DatabaseService,
],
exports: [DatabaseService],
};
}
static forRootAsync(options: {
imports?: any[];
useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;
inject?: any[];
}): DynamicModule {
return {
module: DatabaseModule,
imports: options.imports || [],
providers: [
{
provide: 'DATABASE_OPTIONS',
useFactory: options.useFactory,
inject: options.inject || [],
},
DatabaseService,
],
exports: [DatabaseService],
};
}
}Використання:
typescript
@Module({
imports: [
DatabaseModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
host: config.get('DB_HOST'),
port: config.get('DB_PORT'),
username: config.get('DB_USER'),
password: config.get('DB_PASS'),
database: config.get('DB_NAME'),
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}Шаблон forFeature
Для конфігурації на рівні модуля (наприклад, сутності TypeORM для кожного модуля):
typescript
@Module({})
export class StorageModule {
static forRoot(options: StorageOptions): DynamicModule {
return {
module: StorageModule,
global: true,
providers: [
{ provide: 'STORAGE_OPTIONS', useValue: options },
StorageService,
],
exports: [StorageService],
};
}
static forFeature(bucket: string): DynamicModule {
return {
module: StorageModule,
providers: [
{ provide: 'STORAGE_BUCKET', useValue: bucket },
{
provide: `STORAGE_${bucket.toUpperCase()}`,
useFactory: (storageService: StorageService) => {
return storageService.getBucket(bucket);
},
inject: [StorageService],
},
],
exports: [`STORAGE_${bucket.toUpperCase()}`],
};
}
}typescript
// У функціональних модулях
@Module({
imports: [StorageModule.forFeature('avatars')],
})
export class UsersModule {}
@Module({
imports: [StorageModule.forFeature('documents')],
})
export class DocumentsModule {}Приклад з реального світу: Модуль електронної пошти
typescript
@Module({})
export class EmailModule {
static forRoot(options: EmailModuleOptions): DynamicModule {
return {
module: EmailModule,
global: true,
providers: [
{ provide: 'EMAIL_OPTIONS', useValue: options },
EmailService,
],
exports: [EmailService],
};
}
static forRootAsync(options: EmailAsyncOptions): DynamicModule {
return {
module: EmailModule,
global: true,
imports: options.imports || [],
providers: [
{
provide: 'EMAIL_OPTIONS',
useFactory: options.useFactory,
inject: options.inject || [],
},
EmailService,
],
exports: [EmailService],
};
}
}Підсумок поширених шаблонів
| Метод | Сценарій використання |
|---|---|
forRoot() | Глобальна конфігурація (один раз у AppModule) |
forRootAsync() | Асинхронна конфігурація (з ConfigService тощо) |
forFeature() | Конфігурація на рівні модуля |
register() | Те ж саме, що і forRoot, але може бути викликано кілька разів |
registerAsync() | Асинхронна версія register |
Ключове спостереження: Динамічні модулі — це система плагінів NestJS. Використовуйте
forRootдля одноразової глобальної налаштування,forFeatureдля налаштування на рівні модуля. Асинхронні варіанти дозволяють використовувати впровадження залежностей для отримання значень конфігурації.
Коротка відповідь
Для співбесідиPremium
Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.