Search code examples
dependency-injectionnestjsnestjs-confignest-dynamic-modules

Nestjs can't create dynamic module with config import


I am trying to create a DynamicModule in Nestjs, but it seems I can't properly use useFactory to inject ConfigModule in the process.
If I use a hardcoded boolean instead of config.get('cache').enabled everything works as expected, but I receive the following error if I try to use config:

TypeError: Cannot read properties of undefined (reading 'get')

Here's the code I arranged so far:

app.module.ts

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      validate,
    }),

    CoreModule.registerAsync({
      useFactory: (config: ConfigService) => ({
        cacheEnabled: config.get('cache').enabled,
      }),
      imports: [ConfigModule],
      injects: [ConfigService],
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

core.module.ts

@Module({})
export class CoreModule {
  static registerAsync = (options: {
    useFactory: (...args: any[]) => { cacheEnabled: boolean };
    imports: any[];
    injects: any[];
  }): DynamicModule => {
    const imports = [];
    const providers = [];
    if (options.useFactory().cacheEnabled) imports.push(HttpCacheModule);

    return {
      module: CoreModule,
      imports,
      providers,
    };
  };
}

Solution

  • In your case, you're calling options.useFactory directly: this is supposed to be handled by NestJS's dependency injection, where it will automatically pass the injections and set the return-value to be the provider-value for the class-defined token.

    If you look at the implementation of ConfigurableModuleBuilder, you'll see that it simply returns a new (asynchronous) provider of the form:

    {
        provide: OPTIONS_INJECTION_TOKEN,
        useFactory: options.useFactory,
        inject: options.inject,
    }
    

    The solution is then to then have static imports (only appending consumer-provided imports in dynamic modules), and have providers be dynamically configured (which only works if you have control over the module and providers.

    Alternatively, use environment-variables (or some other pre-NestJS mechanism) to get your configuration for conditionally importing modules (you'll likely want to wait for env-vars with await ConfigModule.envVariablesLoaded).