@gerkirill/config
v1.0.0
Published
Hierarchical configuration management library based on dotenv.
Downloads
2
Readme
Standard configuration library
This module implements configuration management. The configuration is stored in .env format.
Usage
Installation
npm install @gerkirill/config
import path from 'path';
import { loadEnvConfig } from '@gerkirill/config';
import { Environment } from '../models';
const env = loadEnvConfig(path.resolve(__dirname, '../environment'), Environment);
Example model file
import { IsPort } from '@gerkirill/config';
export class Environment {
@IsPort()
API_PORT!: number;
}
Example .env file
API_PORT=3000
Folder with .env files
In the code above - ../environment
is a relative path to the environment folder, containing three .env files:
- .env.default — Contains all env variables. The variables having common values for different environments (prod/qae/int) MUST have a value set. Other variables must be mentioned but have an empty value in this file.
- .env — Contains only environment-specific variables, e.g. Kafka instance host/login/password. We store credentials for the INT env in this file.
- .env.local — Empty by default. The file is used for the development at the local machine only. The library guarantees values from these files will not be ever used in the cloud, because Dockerfile contains
NODE_ENV=production
which makes the library ignore this file.
Configuration files priority
The priority is the following:
- First - values are read from .env.default.
- Second - values present in .env override values from .env.default.
- If
NODE_ENV
isn't set toproduction
- values from .env.local override values from the previous two files.
Configuration validation
It is important to shut down micro-service early in a case no proper configuration provided.
Environment
from the example above is a class with class-validator annotations. It is used to validate the configuration. E.g.:
export class Environment {
@IsPort()
API_PORT!: number;
@IsString()
BUCKET_NAME!: string;
@IsString()
BUCKET_PROJECT_ID!: string;
@IsString()
@IsUrl()
BUCKET_PROXY!: string;
}
Configuration transformation
All the values in the env files are strings. But Environment
must declare them with a proper target type, which could be e.g. number or array of strings. To convert strings to some other representation here is a @Transform annotation provided by the class-transformer. The annotation is re-exported by the @gerkirill/config library. But in order to perform some widely-used transformations - the @gerkirill/config library declares more helper functions, like @Split and @ParseInt:
import { ParseInt, Split, Contains, EACH } from '@gerkirill/config';
export class Environment {
@ParseInt()
S3_PART_SIZE!: number;
@Contains(':', EACH)
@Split(',')
KAFKA_BROKERS!: string[];
}
Above you can also see EACH
constant, defined by the @gerkirill/config library. It is just a handy shortcut to { each: true }
flag used to instruct class validator to apply the rule to each element in the array.
Reasons behind the three .env files
.env.default
API_PORT=5000
BUCKET_NAME=
BUCKET_PROJECT_ID=
BUCKET_PROXY=
.env file exists for two main reasons:
It contains a full list of all config options available. As we have three configuration files - gathering a list of all options required - can be a hard task for the DevOps. Assuming each file has a set of config options, which may (or may not) overlap with options in other files.
It provides default values for the options which aren't environment-specific. This point has two pros. DevOps can write less configuration, in order to provide only environment-specific values. And developers can change default values in one place, w/o the need to update each env-specific configuration in helm files. Even more - no helm update required at all in order to add or change a default value.
.env
This file contains options having environment-specific values. e.g. resource URLs, usernames, passwords (assuming they are different across environments).
BUCKET_NAME=int-ba-origin.sinclairplatform.com
BUCKET_PROJECT_ID=common-int
BUCKET_PROXY=https://int-ba-gateway.sinclairplatform.com
IMPORTANT! We store only INT environment values here, including usernames and passwords. The reason for that is an easy development process. E.g. to develop micro-service locally - the developer just needs to clone the repository, install dependencies and execute npm start
. The micro-service will connect to the INT environment and will be up and running in seconds.
Values for the other environments are set in corresponding .helm files. Secret information, like e.g. passwords and keys - can be stored in some kind of secrets store.
.env.local
Sometimes when you develop locally - you need to adjust some config parameters. E.g. if you want to connect to the local Redis instance instead of the one in INT environment.
Changing .env or .env.default can be dangerous - the changes can be committed to git accidentally, leak to the cloud environment and break something. Despite we keep env.local empty in git (not ignored, just empty) - even if you commit the changes - nothing will happen in the cloud. That's because in the cloud the code is executed in a docker container, which is configured like that:
ENV NODE_ENV=production
And the library ignores env.local in the production environment.