简体   繁体   中英

Best practice to use config service in NestJS Module

I want to use environment variables to configure the HttpModule per module, from the docs I can use the configuration like this:

@Module({
  imports: [HttpModule.register({
    timeout: 5000,
    maxRedirects: 5,
  })],
})

But I don't know what is the best practice to inclue a baseURL from environment vairable (or a config service), for example like this:

@Module({
imports: [HttpModule.register({
    baseURL:  this.config.get('API_BASE_URL'),
    timeout: 5000,
    maxRedirects: 5,
})],

The this.config is undefined here cause it's out of class.

What is the best practice to set baseURL from environment variables (or config service)?

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 .

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.

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 :)

Great answer by @Kim Kern , which clearly goes over injection of the ConfigService into a module configuration, that might be dependent on environment variables; however, from my personal experience, your app-root module or some other module with a couple of imports might get crowded and/or hard to read as well as understand the imports, module configuration and what the module you are defining relies on. So, thanks to Jay McDoniel , who curated me on this question, you can move configuration logic into a separate file .



First Solution


Example of app.module.ts :

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { MikroOrmModule } from '@mikro-orm/nestjs';

import { AppService } from './users.service';
import { AppController } from './users.controller';
import { get_db_config } from './config/database.config';

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

        MikroOrmModule.forRootAsync( get_db_config() ),
    ],
    controllers: [AppController],
    providers:   [AppService],
})
export class AppModule {}

Example of config/database.config.ts :

import { MikroOrmModuleAsyncOptions } from "@mikro-orm/nestjs";
import { ConfigService } from "@nestjs/config";


export function get_db_config(): MikroOrmModuleAsyncOptions
{
    return {
        useFactory: (configService: ConfigService) => 
        ({
            dbName:          'driver',
            type:            'postgresql',
            host:             configService.get('DB_HOST'),
            port:             configService.get('DB_PORT'),
            user:             configService.get('DB_USERNAME'),
            password:         configService.get('DB_PASSWORD'),
            autoLoadEntities: true
        }),
        inject: [ConfigService]
    }
}



However, NestJS Docs - Configuration Namespaces as well as NestJS Authentication and Authorization Course provide an alternative method of solving this issue.



Second Solution


Example of auth.module.ts :

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';

import jwtConfig from './jwt.config';


@Module({
    imports: [
        ConfigModule.forFeature( jwtConfig ),
        JwtModule.registerAsync( jwtConfig.asProvider() ),
    ]
})
export class AuthModule {}

Example of jwt.config.ts :

import { registerAs } from "@nestjs/config"


export default registerAs('jwt', () => {
    return {
        secret:         process.env.JWT_SECRET,
        issuer:         process.env.JWT_TOKEN_ISSUER,
        accessTokenTtl: parseInt(process.env.JWT_TOKEN_TTL)
    };
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM