### Dynamic modules The [Modules chapter](/modules) covers the basics of Nest modules and includes a brief introduction to [dynamic modules](/modules#dynamic-modules). This chapter expands on the subject: what dynamic modules are, how to build them, and when to use them. #### Introduction Most code examples in the **Overview** section of the documentation use regular, or static, modules. Modules define groups of components, such as [providers](/providers) and [controllers](/controllers), that fit together as a modular part of an application. They provide an execution context, or scope, for these components. For example, providers defined in a module are visible to other members of the module without being exported. When a provider needs to be visible outside of a module, it is first exported from its host module, and then imported into its consuming module. Consider a familiar example. First, a `UsersModule` provides and exports a `UsersService`. `UsersModule` is the **host** module for `UsersService`. ```typescript import { Module } from '@nestjs/common'; import { UsersService } from './users.service.js'; @Module({ providers: [UsersService], exports: [UsersService], }) export class UsersModule {} ``` Next, an `AuthModule` imports `UsersModule`, which makes `UsersModule`'s exported providers available inside `AuthModule`: ```typescript import { Module } from '@nestjs/common'; import { AuthService } from './auth.service.js'; import { UsersModule } from '../users/users.module.js'; @Module({ imports: [UsersModule], providers: [AuthService], exports: [AuthService], }) export class AuthModule {} ``` These constructs allow us to inject `UsersService` into, for example, the `AuthService` hosted in `AuthModule`: ```typescript import { Injectable } from '@nestjs/common'; import { UsersService } from '../users/users.service.js'; @Injectable() export class AuthService { constructor(private readonly usersService: UsersService) {} /* Implementation that makes use of this.usersService */ } ``` We'll refer to this as **static** module binding. All the information Nest needs to wire the modules together is declared in the host and consuming modules. Nest makes `UsersService` available inside `AuthModule` by: 1. Instantiating `UsersModule`, including transitively importing the modules that `UsersModule` itself consumes, and transitively resolving any dependencies (see [Custom providers](/fundamentals/custom-providers)). 2. Instantiating `AuthModule`, and making `UsersModule`'s exported providers available to components in `AuthModule` (as if they had been declared in `AuthModule`). 3. Injecting an instance of `UsersService` into `AuthService`. #### Dynamic module use case With static module binding, the consuming module has no way to **influence** how the providers of the host module are configured. This matters for general-purpose modules that need to behave differently for each consumer, much like a "plugin" that requires some configuration before it can be used. As an example, consider a module that generates the public identifiers of your entities. Identifiers that start with a type prefix, such as `usr_` for users and `ord_` for orders, tell you at a glance what kind of entity they refer to. The generation logic is the same for every entity, and only the prefix differs. With static binding, an `IdGeneratorModule` could provide only one generator, configured one way for every module that imports it. What we need instead is for `UsersModule` and `OrdersModule` to each import the module with their own prefix. This is where **dynamic modules** come into play. A dynamic module provides an API for importing one module into another and customizing the properties and behavior of that module at the time it is imported. #### Dynamic module example It's helpful to start from how the module looks from the consuming module's perspective, and then work backwards. A static import leaves no room to pass a prefix: ```typescript import { Module } from '@nestjs/common'; import { IdGeneratorModule } from '../id-generator/id-generator.module.js'; import { UsersService } from './users.service.js'; @Module({ imports: [IdGeneratorModule], providers: [UsersService], }) export class UsersModule {} ``` A dynamic import, on the other hand, passes an options object. Compare the `imports` arrays of the two examples: ```typescript import { Module } from '@nestjs/common'; import { IdGeneratorModule } from '../id-generator/id-generator.module.js'; import { UsersService } from './users.service.js'; @Module({ imports: [IdGeneratorModule.register({ prefix: 'usr' })], providers: [UsersService], }) export class UsersModule {} ``` Let's look at the moving parts of the dynamic example: 1. `IdGeneratorModule` is a normal class, so it must have a **static method** called `register()`. The method is static because it's called on the class, not on an **instance** of the class. The method can have any name, but by convention it's called `register()`, `forRoot()`, or `forFeature()` (see [Community guidelines](#community-guidelines)). 2. We define the `register()` method ourselves, so it can accept any arguments we like. Typically, as here, it accepts an options object. 3. The value returned by `register()` appears in the `imports` array, alongside module classes, so it must be something Nest can treat as a module. In fact, `register()` returns a `DynamicModule`. A dynamic module is a module created at runtime. It has exactly the same properties as the metadata of a static module, plus one additional property called `module`. Here's a sample static module declaration, for comparison: ```typescript @Module({ imports: [DogsModule], controllers: [CatsController], providers: [CatsService], exports: [CatsService], }) ``` A dynamic module is an object with the same properties, plus the `module` property, which references the module class itself. > info **Hint** For a dynamic module, all properties of the module options object are optional **except** `module`. The job of the static `register()` method is therefore to return an object that implements the `DynamicModule` interface. Calling it provides a module to the `imports` array, much like listing a module class does in the static case. The difference is that the module's properties are specified programmatically instead of being fixed in the `@Module()` decorator. Two more details complete the picture: 1. The `imports` array of the `@Module()` decorator accepts not only module classes (e.g., `imports: [UsersModule]`), but also the dynamic modules returned by static methods (e.g., `imports: [IdGeneratorModule.register(...)]`). 2. A dynamic module can itself import other modules. If it depends on providers from other modules, list them in its optional `imports` property, exactly as you would in the `@Module()` decorator of a static module. With this in mind, here's a first version of the dynamic `IdGeneratorModule`: ```typescript import { DynamicModule, Module } from '@nestjs/common'; import { IdGenerator } from './id-generator.js'; @Module({}) export class IdGeneratorModule { static register(): DynamicModule { return { module: IdGeneratorModule, providers: [IdGenerator], exports: [IdGenerator], }; } } ``` Calling `IdGeneratorModule.register()` returns a `DynamicModule` object whose properties are essentially the same as those that, until now, we've provided as metadata through the `@Module()` decorator. > info **Hint** Import `DynamicModule` from `@nestjs/common`. This module isn't configurable yet, though. Let's address that next. #### Module configuration The consuming module passes the options to `register()`, as shown above. The question is how to get them to the component that needs them. `IdGeneratorModule` hosts and exports an injectable service, `IdGenerator`, and it's `IdGenerator` that needs the prefix. For now, the service hard-codes it: ```typescript import { Injectable } from '@nestjs/common'; import { randomUUID } from 'node:crypto'; @Injectable() export class IdGenerator { generate(): string { const options = { prefix: 'usr' }; return `${options.prefix}_${randomUUID()}`; } } ``` Our remaining task is to pass the `options` object from the `register()` method to `IdGenerator`, and we'll use _dependency injection_ to do it. This is the key point: `IdGeneratorModule` provides `IdGenerator`, and `IdGenerator` depends on an `options` object that is supplied only at runtime. So, at runtime, we first bind the `options` object to the Nest IoC container, and then let Nest inject it into `IdGenerator`. As the **Custom providers** chapter explains, providers can [be any value](/fundamentals/custom-providers#non-service-based-providers), not only services, so a plain `options` object works too. First, describe the options with an interface, and define an injection token for them. A token defined as a constant (or a `Symbol`) in a separate file can be imported wherever it's needed: ```typescript @@filename(id-generator.interfaces) export interface IdGeneratorModuleOptions { prefix: string; } export const ID_GENERATOR_OPTIONS = 'ID_GENERATOR_OPTIONS'; ``` Next, bind the options object to the IoC container in the static `register()` method. We are constructing a module dynamically, and one of the properties of a module is its list of providers, so we define the options object as a provider. Pay attention to the `providers` array: ```typescript import { DynamicModule, Module } from '@nestjs/common'; import { IdGenerator } from './id-generator.js'; import { ID_GENERATOR_OPTIONS, IdGeneratorModuleOptions, } from './id-generator.interfaces.js'; @Module({}) export class IdGeneratorModule { static register(options: IdGeneratorModuleOptions): DynamicModule { return { module: IdGeneratorModule, providers: [ { provide: ID_GENERATOR_OPTIONS, useValue: options, }, IdGenerator, ], exports: [IdGenerator], }; } } ``` Now inject the options into `IdGenerator`. A provider registered with a non-class token is injected with the `@Inject()` decorator, as described in [Non-class-based provider tokens](/fundamentals/custom-providers#non-class-based-provider-tokens): ```typescript import { Inject, Injectable } from '@nestjs/common'; import { randomUUID } from 'node:crypto'; import { ID_GENERATOR_OPTIONS, type IdGeneratorModuleOptions, } from './id-generator.interfaces.js'; @Injectable() export class IdGenerator { constructor( @Inject(ID_GENERATOR_OPTIONS) private readonly options: IdGeneratorModuleOptions, ) {} generate(): string { return `${this.options.prefix}_${randomUUID()}`; } } ``` With that in place, each consuming module configures its own generator: ```typescript @Module({ imports: [IdGeneratorModule.register({ prefix: 'usr' })], providers: [UsersService], }) export class UsersModule {} @Module({ imports: [IdGeneratorModule.register({ prefix: 'ord' })], providers: [OrdersService], }) export class OrdersModule {} ``` `UsersService` and `OrdersService` both inject `IdGenerator`, but each receives its own instance, configured by the module that imported it: user identifiers start with `usr_`, and order identifiers with `ord_`. A working example is available in the [dynamic modules sample](https://github.com/nestjs/nest/tree/master/sample/25-dynamic-modules) on GitHub. #### Community guidelines You may have seen methods like `forRoot()`, `register()`, and `forFeature()` in some of the `@nestjs/` packages, and wondered how they differ. There is no hard rule, but the `@nestjs/` packages follow these guidelines: - `register()` configures a dynamic module for use only by the calling module, as our `IdGeneratorModule` does. For example, with `@nestjs/http-client`, `HttpClientModule.register({{ '{' }} baseUrl: 'someUrl' {{ '}' }})` configures an HTTP client for the calling module. Another module can call `HttpClientModule.register({{ '{' }} baseUrl: 'somewhere else' {{ '}' }})` to get a differently configured client. You can do this in as many modules as you want. - `forRoot()` configures a dynamic module once and reuses that configuration in multiple places (possibly without the consumers being aware of it). This is why an application has one `GraphQLModule.forRoot()`, one `TypeOrmModule.forRoot()`, and so on. - `forFeature()` uses the configuration set up by `forRoot()`, but modifies some of it for the needs of the calling module (e.g., which repositories the module has access to, or the context a logger should use). Each of these usually has an asynchronous counterpart, `registerAsync()`, `forRootAsync()`, and `forFeatureAsync()`, which means the same thing, but resolves the configuration through Nest's dependency injection. #### Configurable module builder Manually creating highly configurable dynamic modules that also expose asynchronous methods (`registerAsync()`, `forRootAsync()`, etc.) is complicated, especially for newcomers. To simplify this, Nest provides the `ConfigurableModuleBuilder` class, which constructs a module "blueprint" in a few lines of code. As an example, let's convert the `IdGeneratorModule` above to use the `ConfigurableModuleBuilder`. It keeps the `IdGeneratorModuleOptions` interface, but the builder generates the injection token, so the `ID_GENERATOR_OPTIONS` constant is no longer needed. Create a new file alongside `id-generator.module.ts`, named `id-generator.module-definition.ts`, and use the `ConfigurableModuleBuilder` to construct the `IdGeneratorModule` definition: ```typescript @@filename(id-generator.module-definition) import { ConfigurableModuleBuilder } from '@nestjs/common'; import type { IdGeneratorModuleOptions } from './id-generator.interfaces.js'; export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder().build(); @@switch import { ConfigurableModuleBuilder } from '@nestjs/common'; export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder().build(); ``` Next, change `id-generator.module.ts` to extend the generated `ConfigurableModuleClass`: ```typescript import { Module } from '@nestjs/common'; import { IdGenerator } from './id-generator.js'; import { ConfigurableModuleClass } from './id-generator.module-definition.js'; @Module({ providers: [IdGenerator], exports: [IdGenerator], }) export class IdGeneratorModule extends ConfigurableModuleClass {} ``` As with any dynamic module, the metadata returned by the generated methods extends the metadata in the `@Module()` decorator, so `IdGenerator` is provided and exported alongside the generated options provider. Finally, update `IdGenerator` to inject the generated options provider instead of the `ID_GENERATOR_OPTIONS` token: ```typescript import { Inject, Injectable } from '@nestjs/common'; import { randomUUID } from 'node:crypto'; import type { IdGeneratorModuleOptions } from './id-generator.interfaces.js'; import { MODULE_OPTIONS_TOKEN } from './id-generator.module-definition.js'; @Injectable() export class IdGenerator { constructor( @Inject(MODULE_OPTIONS_TOKEN) private readonly options: IdGeneratorModuleOptions, ) {} generate(): string { return `${this.options.prefix}_${randomUUID()}`; } } ``` Extending `ConfigurableModuleClass` gives `IdGeneratorModule` not only the `register()` method (as the manual implementation did), but also `registerAsync()`, which lets consumers configure the module asynchronously. For example, the following factory reads the prefix with the `ConfigService` from [`@nestjs/config`](/application/configuration): ```typescript import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { IdGeneratorModule } from '../id-generator/id-generator.module.js'; import { UsersService } from './users.service.js'; @Module({ imports: [ IdGeneratorModule.registerAsync({ imports: [ConfigModule], useFactory: (configService: ConfigService) => ({ prefix: configService.getOrThrow('USER_ID_PREFIX'), }), inject: [ConfigService], }), ], providers: [UsersService], }) export class UsersModule {} ``` The `registerAsync()` method takes an object with the following properties: ```typescript { /** * Modules whose exported providers the factory, class, or existing * provider depends on. */ imports?: ModuleMetadata['imports']; /** * Function returning options (or a Promise resolving to options) to configure the * module. */ useFactory?: (...args: any[]) => Promise | ModuleOptions; /** * Dependencies that a Factory may inject. */ inject?: FactoryProvider['inject']; /** * Injection token resolving to a class that will be instantiated as a provider. * The class must implement the corresponding interface. */ useClass?: Type< ConfigurableModuleOptionsFactory >; /** * Injection token resolving to an existing provider. The provider must implement * the corresponding interface. */ useExisting?: Type< ConfigurableModuleOptionsFactory >; /** * List of parent module's providers that will be filtered to only provide necessary * providers for the 'inject' array. */ provideInjectionTokensFrom?: Provider[]; } ``` Let's go through these properties one by one: - `imports` - modules to import into the dynamic module, so that the factory, class, or existing provider can inject their exported providers. The example above imports `ConfigModule` to inject `ConfigService`. - `useFactory` - a function that returns the options object. It can be either synchronous or asynchronous. To inject dependencies into the factory function, use the `inject` property. The example above uses this variant. - `inject` - an array of dependencies to inject into the factory function. The order of the dependencies must match the order of the factory function's parameters. - `useClass` - a class to instantiate as a provider. The class must implement the corresponding interface, which by default means providing a `create()` method that returns the options object. See [Custom options factory class](#custom-options-factory-class) below. - `useExisting` - a variant of `useClass` that reuses an existing provider instead of instructing Nest to create a new instance of the class. The provider must implement the same interface as a `useClass` class (and so must provide the `create()` method, unless you change the method name, as described in [Custom options factory class](#custom-options-factory-class) below). - `provideInjectionTokensFrom` - used together with `useFactory` (or `useExisting`) and `inject`: a list of providers from which Nest picks the ones listed in `inject` and registers them in the dynamic module. This is useful when a module passes its own options on to a nested module that it configures asynchronously. Always choose exactly one of `useFactory`, `useClass`, and `useExisting`, as they are mutually exclusive. #### Custom method key By default, `ConfigurableModuleClass` provides the `register()` method and its `registerAsync()` counterpart. To use a different method name, use the `ConfigurableModuleBuilder#setClassMethodName` method. For example, if your module is meant to be configured once for the entire application, the [community guidelines](#community-guidelines) suggest `forRoot()`: ```typescript @@filename(id-generator.module-definition) export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder() .setClassMethodName('forRoot') .build(); @@switch export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder().setClassMethodName('forRoot').build(); ``` This instructs `ConfigurableModuleBuilder` to generate a class that exposes `forRoot()` and `forRootAsync()` instead: ```typescript @Module({ imports: [ IdGeneratorModule.forRoot({ prefix: 'id' }), // <-- note "forRoot" instead of "register" // or alternatively: // IdGeneratorModule.forRootAsync({ // useFactory: () => ({ prefix: 'id' }), // inject: [...any extra dependencies...], // }), ], }) export class AppModule {} ``` #### Custom options factory class Because the `registerAsync()` method (or `forRootAsync()`, or any other name, depending on the configuration) lets consumers pass a provider definition that resolves to the module options, a consumer can also supply a class that constructs the options object: ```typescript @Module({ imports: [ IdGeneratorModule.registerAsync({ useClass: IdGeneratorConfigService, }), ], }) export class UsersModule {} ``` By default, this class must provide a `create()` method that returns the module options. The `ConfigurableModuleOptionsFactory` interface, exported from `@nestjs/common`, describes this contract: ```typescript import { ConfigurableModuleOptionsFactory, Injectable } from '@nestjs/common'; import type { IdGeneratorModuleOptions } from '../id-generator/id-generator.interfaces.js'; @Injectable() export class IdGeneratorConfigService implements ConfigurableModuleOptionsFactory { create(): IdGeneratorModuleOptions { return { prefix: 'usr' }; } } ``` If your library follows a different naming convention, instruct `ConfigurableModuleBuilder` to expect a different method, for example, `createIdGeneratorOptions`, with the `ConfigurableModuleBuilder#setFactoryMethodName` method: ```typescript @@filename(id-generator.module-definition) export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder() .setFactoryMethodName('createIdGeneratorOptions') .build(); @@switch export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder() .setFactoryMethodName('createIdGeneratorOptions') .build(); ``` Now the `IdGeneratorConfigService` class must expose the `createIdGeneratorOptions()` method instead of `create()`: ```typescript @Module({ imports: [ IdGeneratorModule.registerAsync({ useClass: IdGeneratorConfigService, // <-- this class must provide the "createIdGeneratorOptions" method }), ], }) export class UsersModule {} ``` #### Extra options In some cases, a module needs extra options that determine how it behaves, but that shouldn't be included in the `MODULE_OPTIONS_TOKEN` provider, because they are irrelevant to the providers registered within the module. A good example is an `isGlobal` flag: `IdGenerator` doesn't need to know whether its host module is registered as a global module. For such options, use the `ConfigurableModuleBuilder#setExtras` method: ```typescript export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder() .setExtras( { isGlobal: false, }, (definition, extras) => ({ ...definition, global: extras.isGlobal, }), ) .build(); ``` The first argument passed to the `setExtras` method is an object with the default values of the extra properties. The second argument is a function that receives the auto-generated module definition (with `providers`, `exports`, etc.) and the `extras` object, which holds the extra properties (either specified by the consumer or the defaults). The function returns the modified module definition. In this example, the `extras.isGlobal` property is assigned to the `global` property of the module definition, which determines whether the module is global (see [Dynamic modules](/modules#dynamic-modules) in the Modules chapter). Now, when importing this module, consumers can pass the additional `isGlobal` flag. For example, an application that uses one prefix for all of its identifiers can register the module once, globally: ```typescript @Module({ imports: [ IdGeneratorModule.register({ isGlobal: true, prefix: 'id', }), ], }) export class AppModule {} ``` However, since `isGlobal` is declared as an extra property, it isn't part of the options object provided by `MODULE_OPTIONS_TOKEN`: ```typescript @Injectable() export class IdGenerator { constructor( @Inject(MODULE_OPTIONS_TOKEN) private readonly options: IdGeneratorModuleOptions, ) { // "options" object will not have the "isGlobal" property // ... } } ``` #### Extending auto-generated methods The auto-generated static methods (`register()`, `registerAsync()`, etc.) can be extended if needed, as follows: ```typescript import { DynamicModule, Module } from '@nestjs/common'; import { IdGenerator } from './id-generator.js'; import { ASYNC_OPTIONS_TYPE, ConfigurableModuleClass, OPTIONS_TYPE, } from './id-generator.module-definition.js'; @Module({ providers: [IdGenerator], exports: [IdGenerator], }) export class IdGeneratorModule extends ConfigurableModuleClass { static register(options: typeof OPTIONS_TYPE): DynamicModule { return { // your custom logic here ...super.register(options), }; } static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule { return { // your custom logic here ...super.registerAsync(options), }; } } ``` Note the `OPTIONS_TYPE` and `ASYNC_OPTIONS_TYPE` types, which must be exported from the module definition file: ```typescript export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE, ASYNC_OPTIONS_TYPE, } = new ConfigurableModuleBuilder().build(); ```