Best practice to use config service in NestJS Module

I also encountered several issues with implementing a ConfigService as described in the NestJS documentation (no type-safety, no modularity of configuration values, ...), I wrote down our company's final NestJS configuration management strategy in great detail here: NestJS Configuration Management

The basic idea is to have a central config module that loads all configuration values from the processes' environment. However, instead of providing a single service to all modules, each module can inject a dedicated subset of the configuration values! So each module contains a class that specifies all configuration values that this module needs to be provided at runtime. This simultaneously gives the developer type-safe access to configuration values (instead of using string literals throughout the codebase)

Hope this pattern also works for your use-case :)


Although the top rated answer to this question is technically correct for most implementations, users of the @nestjs/typeorm package, and the TypeOrmModule should use an implementation that looks more like the below.

// NestJS expects database types to match a type listed in TypeOrmModuleOptions
import { TypeOrmModuleOptions } from '@nestjs/typeorm/dist/interfaces/typeorm-options.interface';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [mySettingsFactory],
    }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        type: configService.get<TypeOrmModuleOptions>('database.type', {
          infer: true, // We also need to infer the type of the database.type variable to make userFactory happy
        }),
        database: configService.get<string>('database.host'),
        entities: [__dirname + '/**/*.entity{.ts,.js}'],
        synchronize: true,
        logging: true,
      }),
      inject: [ConfigService],
    }),
  ],
  controllers: [],
})
export class AppRoot {
  constructor(private connection: Connection) {}
}

The major thing this code is doing is retrieving the correct typings from TypeORM (see the import) and using them to hint the return value configService.get() method. If you don't use the correct TypeORM typings, Typescript would get mad.


Update Jan 19

HttpModule.registerAsync() was added in version 5.5.0 with this pull request.

HttpModule.registerAsync({
  imports:[ConfigModule],
  useFactory: async (configService: ConfigService) => ({
    baseURL:  configService.get('API_BASE_URL'),
    timeout: 5000,
    maxRedirects: 5,
  }),
  inject: [ConfigService]
}),

Original Post

This problem was discussed in this issue. For the nestjs modules like the TypeOrmModule or the MongooseModule the following pattern was implemented.

The useFactory method returns the configuration object.

TypeOrmModule.forRootAsync({
  imports:[ConfigModule],
  useFactory: async (configService: ConfigService) => ({
    type: configService.getDatabase()
  }),
  inject: [ConfigService]
}),

Although Kamil wrote

Above convention is now applied in all nest modules and will be treated as a best practice (+recommendation for 3rd party modules). More in the docs

it does not seem to be implemented for the HttpModule yet, but maybe you can open an issue about it. There are also some other suggestions in the issue I mentioned above.

Also have a look at the official docs with best practices on how to implement a ConfigService.