-
Notifications
You must be signed in to change notification settings - Fork 6
Api
The public API for the ConfigManager, which is inherited by the ConfigService
(name it whatever you want) that you implement, has only two methods.
The ConfigManager builds a hash of the environment variables it parses,
cascades and validates. This hash (which is private to the
ConfigManager) ends up looking something like this:
{
'DB_HOST': 'localhost',
'DB_USER': 'devdbuser',
'DB_PASS': 'devdbpass',
'DB_PORT': 3278
}The ConfigManager (and therefore your ConfigService) provides a type-safe
get<T>() method to get a value from the hash. Use it like this:
import { Injectable } from '@nestjs/common';
import { ConfigService } from './modules/config/config.service';
@Injectable()
export class AppService {
private readonly DB_HOST: string;
private readonly DB_USER: string;
private readonly DB_PASS: string;
private readonly DB_PORT: number;
constructor(private readonly configService: ConfigService) {
this.DB_HOST = configService.get<string>('DB_HOST');
this.DB_USER = configService.get<string>('DB_USER');
this.DB_PASS = configService.get<string>('DB_PASS');
this.DB_PORT = configService.get<number>('DB_PORT');
}
}You may want to add custom methods to your ConfigService to make it easier to integrate with other parts of your application.
Note: see https://github.com/nestjs/nest/issues/1300#issuecomment-444870454 for more from the NestJS author on how to inject into a Dynamic Module]
As a convenience, you may want to provide methods that return composed objects representing configuration options for services like database services and other dynamically configured modules. You can easily inject NestJSConfigManager into these modules using standard Nest Dependency Injection
For example, consider this example of TypeOrm module registration from the Nest documentation:
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
type: 'mysql',
host: configService.getString('HOST'),
port: configService.getString('PORT'),
username: configService.getString('USERNAME'),
password: configService.getString('PASSWORD'),
database: configService.getString('DATABASE'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
inject: [ConfigService],
});To make this a little more concise, you could implement a createTypeOrmOptions()
method in your ConfigService (shown below), and simplify the module
registration to:
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) =>
configService.createTypeOrmOptions(),
inject: [ConfigService],
});Your ConfigService to implement this would look like (note the
createTypeOrmOptions() method):
// src/config/config.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigManager } from '@nestjsplus/config';
import * as Joi from 'joi';
@Injectable()
export class ConfigService extends ConfigManager {
provideConfigSpec() {
return {
DB_HOST: {
validate: Joi.string(),
required: false,
default: 'localhost',
},
DB_PORT: {
validate: Joi.number()
.min(5000)
.max(65535),
required: false,
default: 5432,
},
DB_USERNAME: {
validate: Joi.string(),
required: true,
},
DB_PASSWORD: {
validate: Joi.string(),
required: true,
},
DB_NAME: {
validate: Joi.string(),
required: true,
},
};
}
// return the options expected by the TypeOrmModule's
// forRootAsync() configuration method
public createTypeOrmOptions() {
return {
type: 'mysql',
host: this.get<string>('DB_HOST'),
port: this.get<number>('DB_PORT'),
username: this.get<string>('DB_USERNAME'),
password: this.get<string>('DB_PASSWORD'),
database: this.get<string>('DB_NAME'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}
}
}You can create any number of these dynamic module configuration methods. For example,
for the @nestjs/jwt module's JwtModule, you might want:
// in the module where you register the JwtModule
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) =>
configService.createJwtOptions(),
inject: [ConfigService],
}),And in your ConfigService:
.
.
.
public createJwtOptions() {
return {
signOptions: {
expiresIn: this.get<number>('JWT_EXPIRATION')
}
secret: this.get<string>('JWT_SECRET'),
};
}
.
.
.You can also create a namespaced API to your environment variables.
// src/config/config.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigManager } from '@nestjsplus/config';
import * as Joi from 'joi';
@Injectable()
export class ConfigService extends ConfigManager {
provideConfigSpec() {
return {
DB_HOST: {
validate: Joi.string(),
required: false,
default: 'localhost',
},
DB_PORT: {
validate: Joi.number()
.min(5000)
.max(65535),
required: false,
default: 5432,
},
DB_USERNAME: {
validate: Joi.string(),
required: true,
},
DB_PASSWORD: {
validate: Joi.string(),
required: true,
}
DB_NAME: {
validate: Joi.string(),
required: true,
},
};
}
getDB() {
return {
HOST: this.get('DB_HOST'),
PORT: this.get('DB_PORT'),
USERNAME: this.get('DB_USERNAME'),
PASSWORD: this.get('DB_PASSWORD'),
DATABASE: this.get('DB_NAME'),
}
}
}To use this elsewhere in your code, you could write:
const DB = configService.getDB();
console.log(`DB Name = ${DB.DATABASE}`);
// alternatively
console.log(`DB NAME = ${configService.getDB().DATABASE}`);The trace method returns a hash object called a resolveMap that looks like the
sample below. It's primary purpose is to debug issues with how the environment
has been resolved at runtime, where it can sometimes be tricky to discover how
the cascade resolved.
For each environment variable in the schema, resolveMap has an object keyed
by that variable name, with 6 properties:
-
dotenv- shows the value for this variable in the.envfile; shows'--'if there is none -
env- shows the value for this variable in the external environment; shows'--'if there is none -
default- shows the default value for this variable provided by the schema; shows'--'if there is none -
resolvedFrom- shows how the environment variable was resolved (from the.envfile, the external environment, or the default value from the schema) -
isExtra- true if the field is present in the.envfile but not in the schema andallowExtrasis set totrue -
resolvedValue- shows the final resolved value for the environment variable
A `resolveMap` looks like this:
{
DB_HOST: {
dotenv: '--',
env: '--',
default: 'localhost',
resolvedFrom: 'default',
isExtra: false,
resolvedValue: 'localhost'
},
DB_PORT: {
dotenv: '--',
env: '--',
default: 5432,
resolvedFrom: 'default',
isExtra: false,
resolvedValue: 5432
},
DB_USERNAME: {
dotenv: 'john',
env: '--',
default: '--',
resolvedFrom: 'dotenv',
isExtra: false,
resolvedValue: 'john'
},
DB_PASSWORD: {
dotenv: 'mypassword',
env: '--',
default: '--',
resolvedFrom: 'dotenv',
isExtra: false,
resolvedValue: 'mypassword'
},
DB_NAME: {
dotenv: 'mydb',
env: '--',
default: '--',
resolvedFrom: 'dotenv',
isExtra: false,
resolvedValue: 'mydb'
},
}