How to dynamically inject providers in NestJs

Viewed 2861

My team is trying to identify a way to dynamically DI such that there can a bulk insertion point without a need to spell out + import each Module.

@Module({
  ...
  providers: [
    WorkerService,
    WorkerResolver,
    Worker2Service,
    Worker2Resolver........
  ]
})

want to achieve

var allModules = ... // logic here to include all my resolvers, or all my services

@Module({
  ...
  providers: [
    ...allModules
  ]
})
1 Answers

You can use glob package to dynamically find modules then use NestJs dynamic module feature to load them dynamically.

Suppose all of your worker files are stored in a directory named workers and with extention .worker.ts:

    @Module({})
    export class WorkerModule {
      static forRootAsync(): DynamicModule {
        return {
          module: WorkerModule ,
          imports: [WorkerCoreModule.forRootAsync()],
        };
      }
    }
export class WorkerCoreModule {

  static async forRootAsync(): Promise<DynamicModule> {
    // Feel free to change path if your structure is different
    const workersPath = glob.sync('src/**/workers/*.worker.ts');
    const workersRelativePathWithoutExt = modelsPath
      // Replace src, because you are probably running the code
      // from dist folder
      .map((path) => path.replace('src/', './../'))
      .map((path) => path.replace('.ts', ''));
    const workerProviders: Provider<any>[] = [];
    const importedModules = await Promise.all(
      workersRelativePathWithoutExt.map((path) => import(path)),
    );
    importedModules.forEach((modules) => {
      // Might be different if you are using default export instead
      const worker = modules[Object.keys(modules)[0]];
      workerProviders.push({
        provide: worker.name, 
        useValue: worker,
      });
    });

    return {
      module: WorkerCoreModule,
      providers: [...workerProviders],
      // You can omit exports if providers are meant to be used
      // only in this module
      exports: [...workerProviders],
    };
  }
}

Now suppose you have a simple worker class with path src/anyModule/workers/simple-worker.ts, you can use it like this:

class WrokersService {
  constructor(@Inject('SimpleWorker') simpleWroker: SimpleWorker) {}
  .
  .
  .
}

If you want to omit @Inject('SimpleWorker') and automatically inject modules like NestJs services then you need to make these changes to WorkerCoreModule:

workerProviders.push({
  provide: worker,
});

However in order for this to work you need to be sure that your workers classes are decorated with @injectable().

Related