A NestJS module to manage runtime configuration
Install the core package
npm install --save-dev @sonyahon/nestjs-config
yarn add @sonyahon/nestjs-config
Install required additional modules
npm install --save-dev @sonyahon/nestjs-config-dotenv
yarn add @sonyahon/nestjs-config-dotenv
The list of available official modules can be found here
General usage:
// app.config.ts
import { Config } from '@sonyahon/nestjs-config';
@Config()
export class AppConfig {
public appPort: number = 9090;
public appHost: string = '0.0.0.0';
}
// app.module.ts
import { ConfigModule } from '@sonyahon/nestjs-config';
@Module({
imports: [
ConfigModule.register({
configs: [AppConfig],
plugins: []
}),
// rest of your imports
],
// rest of your configuration for the main app module
})
export class AppModule {}
// some-provider.service.ts
@Injectable()
export class SomeProvider {
constructor(
@InjectConfig(AppConfig)
private readonly appConfig: AppConfig
) {}
getAppUrl() {
return `http://${this.appConfig.appHost}:${this.appConfig.appPort}`; // http://0.0.0.0:9090
}
}
The core as is does not provide much features. All of them are provided via plugins. Please see documentation for a concrete module here. Here is an example for dotenv-module
// app.config.ts
@Config()
@DotenvPrefix('APP')
export class AppConfig {
@DotenvVar('PORT', Transform.Number)
appPort: number = 9090;
@DotenvVar('HOST')
appHost: string = '0.0.0.0';
}
// .env
APP__PORT=6969
APP__HOST=127.0.0.1
// app.module.ts
@Module({
imports: [
ConfigModule.register({
configs: [AppConfig],
plugins: [new DotenvPlugin()]
}),
// rest of your imports
],
// rest of your configuration for the main app module
})
export class AppModule {}
A plugin to fetch enviroment variables
npm i --save-dev @sonyahon/nestjs-config-dotenv
yarn add @sonyahon/nestjs-config-dotenv
Inlcuding plugin:
// app.module.ts
@Module({
imports: [
ConfigModule.register({
configs: [...Configs],
plugins: [...OtherPlugins, new DotenvPlugin()]
})
],
})
export class AppModule {}
Dotenv plugin adds 2 decorators for the you to use:
@DotenvVar(name: string, transofmer?: (value: string) => any)
name
- environemt variable name e.g.HOST
orPORT
trasnformer
- optional transformer function, to turn env string into something else, see included transformers
Example:
As a result of this plugin transformation, props decorated withimport { DotenvVar } from '@sonyahon/nestjs-config-dotenv'; @Config() class EnvConfig{ @DotenvVar('HOST') appHost: string = 'localhost'; @DotenvVar('PORT', v => parseInt(v)) appPort: number = 9090; }
DotenvVar
will get the value ofprocess.env.[name]
.@DotenvPrefx(name: string)
name
- prefix name to use
In some cases we want to use prefixes likeAPP__[NAME]
for out env variables. Example:
In the example aboveimport { DotenvVar, DotenvPrefix } from '@sonyahon/nestjs-config-dotenv'; @Config() @DotenvPrefix('ENV') class EnvConfig{ @DotenvVar('PROP') prop: string = 'value'; }
prop
property will get the value ofprocess.env.ENV__PROP
enviroment variable
There is a handfull of ready to use transformers already available:
Transformers.IntoInteger
- string -> int conversion e.g.'123' -> 123
Transformers.IntoNumber
- string -> float conversion e.g.'10.2' -> 10.2
Transformers.IntoBoolean
- string -> boolean conversion'true' -> true
'false' -> false
Transformers.IntoCommaSeperatedStringList(innerTransformer?: (v: string) => any))
a transformer generator functions that splits passed string by comma, to get an array of data and appliesinnerTransformer
to each of them if needed e.g.Transformers.CommaSeperatedStringList(Transformers.IntoInteger)
will produce'123,234,456' -> [123, 234, 456]
A plugin to fetch files as config prop values
npm i --save-dev @sonyahon/nestjs-config-files
yarn add @sonyahon/nestjs-config-files
Inlcuding plugin:
// app.module.ts
@Module({
imports: [
ConfigModule.register({
configs: [...Configs],
plugins: [...OtherPlugins, new FilesPlugin()]
})
],
})
export class AppModule {}
FilesPlugin
adds 1 new decorator:
@FileVar(filePath: string, throwIfErrorReading: boolean = false)
filePath
- file path to read file fromthrowIfErrorReading
- by default if some error happens during reading of a file, it will be logged to stdout viaconsole.warn
, but no error will be thrown and the default value provided in the config will be used. In case you want to stop startup of Nest application and receive the thrown error, set this totrue
Example:
In the exampel above,import { FileVar } from '@sonyahon/nestjs-config-files'; @Config() class EnvConfig{ @FileVar('app/resources/certificate.crt') cert: string = 'demo-certificate-text-here'; @FileVar('app/resources/another-file.txt', true) anotherFile: string; }
cert
will have the contents of theapp/resources/certificate.crt
file the'demo-certificate-text-here'
if the file was not found.anotherFile
will have theapp/resources/another-file.txt
file contents and will throw if this file is not presented.
A plugin to fetch data from the network
npm i --save-dev @sonyahon/nestjs-config-fetch
yarn add @sonyahon/nestjs-config-fetch
Inlcuding plugin:
// app.module.ts
@Module({
imports: [
ConfigModule.register({
configs: [...Configs],
plugins: [...OtherPlugins],
asyncPlugins: [...OtherAsyncPlugins, FetchPlugin.Provider]
})
],
})
export class AppModule {}
FetchPlugin
adds 1 new decorator:
@FetchVar(url: string, parser: (data: Response) => Promise<any>, throwIfErrorFetching: boolean = false)
url
- url to fetchparser
- a function used to parseResponse
to get the actual datathrowIfErrorFetching
- by default if some error happens during fetching, it will be logged to stdout viaconsole.warn
, but no error will be thrown and the default value provided in the config will be used. In case you want to stop startup of Nest application and receive the thrown error, set this totrue
Example:
import { FileVar } from '@sonyahon/nestjs-config-fetch'; @Config() class FetchConfig{ @FetchVar('https://swapi.dev/api/people/1', async (resp) => { const json = await resp.json(); return json.name; }) heroName!: string; }
GET https://swapi.dev/api/people/1
will return a JSON, containing{..., name: "Luke Skywalker"}
, so theheroName
variable will have this value.
NOTE (1): NodeJSfetch
does not throw if there was a valid response from the request e.g.400
or500
status is a valid request, so please be sure to handle this requests in theparser
.
NOTE (2):throwIfErrorFetching
will consume errors thrown in theparser
too. So if set tofalse
and thrown an error in theparser
the config property will just receive the default value. If set totrue
the error thrown in theparser
will halt startup of NestJS application.
In order to create a custom module several helper utilities are provided in @sonyahon/nestjs-config
package. There can be 2 types of plugins: Sync
and Async
. Below are two examples for both. Also please take a look into the source code of Available modules as an example.
Sync plugin is just a class which implements the IPlugin
interface. For this example lets create a plugin that has an ability to multiply a number config value by passed factor
// multiplier-plugin.ts
import { IPlugin, makePropertyDecorator } from '@sonyahon/nestjs-config';
const [MultiplyBy, fetchMultiplyByVars] = makePropertyDecorator((factor: number) => {
return { factor }
});
class MultiplierPlugin implements IPlugin {
apply(target: any) {
const props = fetchMultiplyByVars(target);
props.forEach(({property, value, params: { factor }}) => {
target[property] = value * factor;
});
}
}
// app.config.ts
@Config()
export class AppConfig {
@MultiplyBy(2)
value = 10
}
Explanation:
makePropertyDecorator
- takes in a function that does 2 things
- Creates a type definition for the decorator function
- Provivdes a way to convert passed arguments to
params
property
makePropertyDecorator
returns a decorator (first element of returned array) and function (second element of returned array) to retrieve all properties of config instanse for this decorator.
The task of apply(target: any)
function, is to modify passed config instance. In this example we are fetching all propeties marked by @MultiplyBy
decorator and multiplying the default configuration value by passed factor.
Async plugins are created the same way as sync plugins. The difference is that they need to implement IAsyncPlugin
interface instead.
// multiplier-plugin.ts
import { IAsyncPlugin, makePropertyDecorator } from '@sonyahon/nestjs-config';
const [MultiplyBy, fetchMultiplyByVars] = makePropertyDecorator((factor: number) => {
return { factor }
});
class MultiplierPlugin implements IAsyncPlugin {
async apply(target: any) {
const props = fetchMultiplyByVars(target);
props.forEach(({property, value, params: { factor }}) => {
target[property] = value * factor;
});
}
}
// app.config.ts
@Config()
export class AppConfig {
@MultiplyBy(2)
value = 10
}
This allows one to have an async function for the apply method.
NOTE: Async modules are called during OnApplicationBoostrap
lifecycle event, so if one tries to get a value controlled by an async plugin before this event, they would get the default value or undefined.
In some cases values in the configuration can change in runtime. For such cases there is a special provider to update configuration values.
// update-watcher.ts
import { RequestUpdateProvider } from '@sonyahon/nestjs-config';
import {SomeNestjsPluginThatRequiresUpdate} from 'plugin';
@Injectable()
export class UpdateWatcherService {
constructor(
private readonly requestUpdateProvider: RequestUpdateProvider
) {}
updateHappened() {
// check if update is needed
}
async check() {
if (this.updateHappened()) {
await this.requestUpdateProvider.requestUpdateFor(SomeNestjsPluginThatRequiresUpdate);
}
}
}
This call will re-apply passed plugin, so the value could be updated.