Dynamic Modules: forRoot(), forFeature(), and ConfigurableModuleBuilder
- Last updated
- Prerequisites:
- NestJS Modules and Providers
- TypeScript Generics and Factory Providers
- nestjs
- dynamic-modules
- forRoot
- forFeature
- dependency-injection
- architecture
Read at your depth
The practical view
Dynamic modules allow you to configure NestJS modules at runtime with custom options. Common conventions include `forRoot()` (configures a singleton module globally, like `DatabaseModule.forRoot({ url: '...' })`), `forFeature()` (registers specific entities/tokens within a domain module, like `TypeOrmModule.forFeature([User])`), and `forRootAsync()` (loads configuration asynchronously via `useFactory` and `ConfigService`). NestJS provides `ConfigurableModuleBuilder` to generate dynamic module boilerplate automatically.
The same idea in other frameworks
`DatabaseModule.forRoot({ url })` returning `DynamicModule` with factory providers.Angular `RouterModule.forRoot(routes)` / `RouterModule.forChild(routes)` pattern.Spring Boot `@Configuration` classes with `@ConditionalOnProperty` and dynamic `@Bean` methods.FastAPI parameterized dependency factory functions (`def get_db(settings): ...`).Legacy vs modern
Hardcoded Static Module vs ConfigurableModuleBuilder Dynamic Module
Hardcoded modules cannot receive dynamic options from `ConfigService`; dynamic modules allow runtime configuration injection.
@Module({ providers: [{ provide: 'CONFIG', useValue: { host: 'localhost' } }] }) // ❌ Hardcoded configurationexport class AuthModule extends ConfigurableModuleClass {} // ✅ NEW: Generates forRoot / forRootAsync automaticallyInterview gotchas
Context
Interviewers test your knowledge of modular architecture conventions in enterprise NestJS apps.
The mechanical answer
`forRoot()` is meant to be called **once in the root `AppModule`** to configure a singleton infrastructure service globally (e.g. database connection pool, global JWT configuration). `forFeature()` is called in **domain feature modules** (e.g. `UsersModule`, `OrdersModule`) to register specific domain sub-resources (like repositories or message queues) that depend on the global connection configured by `forRoot()`.
Trap
Calling `forRoot()` in multiple child feature modules will create duplicate connection pools and multiple singleton instances, potentially leaking database connections.
Context
Checks knowledge of module metadata flags.
The mechanical answer
Set `global: true` on the returned `DynamicModule` object: `return { global: true, module: MyModule, providers: [...], exports: [...] };` or apply the `@Global()` decorator on the module class. Once imported in the root `AppModule`, its exported providers are available everywhere.
Trap
Overusing `global: true` leads to tightly coupled spaghetti architecture; reserve it strictly for core infrastructure (logger, config, database).