This is a config module for NestJS
npm install @sonyahon/config
// configs/app.config.ts
@Config('APP') // Prefix of .env keys
export class AppConfig {
@EnvVar('PORT') // Postfix of .env keys. In this keys APP__PORT env will be used
@Integer()
public port = 6969; // One can provide a default value. It will be used if no environmnet variable is present
}
// main.module.ts
@Module({
imports: [ConfigModule.forRoot([AppConfig], {
defineGlobal: true, // Default is false, if true this will be @Global
useFile: './config.env', // Use this if u want to pass .env file directly
useString: 'APP__PORT=6969', // Use this if u want to pass .env-like string
// Default usage is to look into process.env via 'dotenv' package
})]
})
export class MainModule {
}
// some.service.ts
@Injectable()
export class SomeService {
constructor(
@InjectConfig(AppConfig)
private readonly appConfig: AppConfig,
) {}
}
There are several ways to get the needed config value:
// Get by config token (in injects, app.get, etc)
const config: AppConfig = app.get(getConfigToken(AppConfig));
// Or by config value token
const redisPort: number = app.get(getConfigValueToken(RedisConfig, 'port'));
// Via decorators
@Injectable()
export class SomeService {
constructor(
@InjectConfig(AppConfig) // Get the whole config
private readonly appConfig: AppConfig,
@InjectValue(RedisConfig, 'port')
private readonly redisPort: number,
) {
}
}
Each class decorated with @Config(prefix?: string)
decorator is considered
a config. Note that the prefix
parameter is optional.
Examples:
@Config('APP') // APP__* env variables will be used for this config
export class AppConfig {
...
}
@Config() // Will use only variables which are described via `@EnvVar` decorator
export class NoPrefixConfig {
...
}
If your property is provided via the ENV variable, you should use the @EnvVar
decorator
...
@EnvVar('VARIABLE') // In case there were no prefix in the `Config` decorator this will look app just the "VARIABLE" env variable. If there was some prefix, then "PREFIX__VARIABLE" env will be used.
public variable = 'some-default-value'; // You can provide a default value, which will be used if the desired env variable is not presented
...
There are some default validation decorators for properties:
@Integer(options?: {from?: number, to?: number})
: ensures that the passed variable is an integer. You can optionally passfrom
andto
options to ensure that this integer is in range.@String(options?: {withLength?: number, from?: number, to?: number})
: ensures that the passed variable is a string with specified length.@AnyNumber(options?: {from?: number, to?: number})
: same as@Integer
but accepts floats too.@Boolean()
: tries to convert a string "true"/"false" to a boolean value. Note that it can accept only this 2 strings.JavaScriptDate()
: tries to convert input to a JavaScriptDate. Throws if this conversion fails@Initialize(initializer: (value: string) => any)
: applies initializer to the value.@Email()
: Validates the string w/ a email RegExp. Examples:
...
@EnvVar('PORT') // Looks for the "PORT" env
@Integer() // Ensures that it is an Int
public port = 5432; // Default value if "PORT" env is not provided
...
...
@EnvVar('PASSWORD')
@String({from: 8})
public port = "suppa"; // This will throw an error in case the "PASSWORD" env is not found or it is too short. Cause the default value "suppa" will not pass the "{from: 8}" validation test
Sometimes the included validators are not enough. In this case you can leverage these to methods:
createPropValidator(validator: (key: string, value: any, options?: any) => any)
Note 1: Remember that value has type of any
because the default values are going throw these validations too.
Note 2: If you will throw ConfigModuleValidationException
it will be handled to show better error reports. It will look like this:
Exception: {PASSED ERROR MESSAGE}. Where: SomeConfig::app. Passed value: <ACTUAL VALUE> with type of "string"
// Exmaple of implementing a simple validator via createPropValidator
const TwoOrEight = createPropValidator((key, value: any, options?: {fallbackToTwo: boolean}) => {
if (value === 2 || value === 8) return value; // No need to do any work. Probably one of the default values;
const num = parseInt(value);
if (isNan(num) || num !== 2 || num !== 8) {
if (options.fallbackToTwo) { // fallbackToTwo -> just return the fallback value
return 2;
}
throw new ConfigModuleValidationException('value is not 2 or 8'); // Passed value is not a number or it is not equal to 2 or 8.
}
return value;
});
// Somewhere in config:
@Config()
export class Configuration {
@EnvVar('TWO_OR_EIGHT')
@TwoOrEight()
public twoOrEight = 2;
}
createRegExpPropValidator(regexp: RegExp, name?: string)
This is used if you need to use a regexp validation. name
is used in the error messages
// Example of implementing a simple regexp validator via createRegExpPropValidator
const ContainsAPP = createRegExpPropValidator(/.*APP.*/, 'ContainsAPP');
// Using
@Config()
export class SomeConfig {
@EnvVar('APP_NAME')
@ContainsAPP()
public app;
}
// APP_NAME="DOES NOT CONTAIN NEEDED INFO" node main.js
// Exception: value does not match ContainsAPP regexp. Where: SomeConfig::app. Passed value: <DOES NOT CONTAIN NEEDED INFO> with type of "string"