Dynamic Modules

Dynamic modules are configured at import time with a static method like forRoot or register.

Syntaxstatic forRoot(options): DynamicModule

A dynamic module is a module you configure when you import it. Instead of importing the class directly, you call a static method that returns the module with your options baked in.

Common conventions

  • forRoot(options) - configure a module once at the app root, e.g. a database connection.
  • forFeature(options) - register per-feature pieces, e.g. specific entities.
  • register(options) - general configurable import.

These methods return a DynamicModule object describing the providers and exports for that configuration.

Example

Example Β· typescript
import { Module, DynamicModule } from '@nestjs/common';

@Module({})
export class ConfigModule {
  static forRoot(options: { folder: string }): DynamicModule {
    return {
      module: ConfigModule,
      providers: [
        { provide: 'CONFIG_OPTIONS', useValue: options },
      ],
      exports: ['CONFIG_OPTIONS'],
    };
  }
}

// Usage: imports: [ConfigModule.forRoot({ folder: './config' })]

When to use it

  • A developer configures the ConfigModule once at the root with ConfigModule.forRoot({ envFilePath: ".env" }) so all other modules can inject ConfigService.
  • A team uses TypeOrmModule.forRootAsync() to asynchronously load database credentials from ConfigService before establishing the connection.
  • A library author writes a custom SmsModule.register({ apiKey }) pattern so consumers can pass credentials at import time.

More examples

forRoot static method pattern

Implements the forRoot/register pattern: consumers call SmsModule.register({ apiKey: "..." }) to pass config at import time.

Example Β· ts
import { Module, DynamicModule } from '@nestjs/common';

@Module({})
export class SmsModule {
  static register(options: { apiKey: string }): DynamicModule {
    return {
      module: SmsModule,
      providers: [
        { provide: 'SMS_OPTIONS', useValue: options },
      ],
      exports: ['SMS_OPTIONS'],
    };
  }
}

Consuming a dynamic module

Shows how a consuming module calls the static register method to pass runtime configuration to the dynamic module.

Example Β· ts
import { Module } from '@nestjs/common';
import { SmsModule } from './sms/sms.module';

@Module({
  imports: [
    SmsModule.register({ apiKey: process.env.SMS_API_KEY! }),
  ],
})
export class AppModule {}

forRootAsync with ConfigService

Uses forRootAsync to delay TypeORM configuration until ConfigService is available, enabling async environment variable loading.

Example Β· ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (cfg: ConfigService) => ({
        type: 'postgres',
        url: cfg.get('DATABASE_URL'),
        autoLoadEntities: true,
      }),
    }),
  ],
})
export class AppModule {}

Discussion

  • Be the first to comment on this lesson.