@z-brain/typed-node-env
v1.0.6
Published
π₯ Strictly typed access and type-casting for ENV variables π
Maintainers
Readme
Typed Node ENV
:package: Installation
yarn add @z-brain/typed-node-env
ornpm i -s @z-brain/typed-node-env
:running: Get started
Create a config class with decorated properties config.ts
import { EnvString, EnvInteger, EnvBoolean, Environment } from '@z-brain/typed-node-env';
@Environment()
export class EnvConfig {
@EnvString()
public readonly host!: string;
@EnvInteger()
public readonly port!: number;
@EnvBoolean()
public readonly production!: boolean;
}
const config = new EnvConfig();
console.log(
config.host === 'test.net',
config.port === 3000,
config.production === false,
);Due to the used decorators, the values for all properties can now be set via environment variables:
HOST=test.net PORT=3000 PRODUCTION=false ts-node config.ts:tada: Benefits
- Type safety for your configuration:
- No need to maintain a separate TypeScript interface
- Types can be infered from the property's default value
- Enforce correct types for values passed by environment variables and notifies about errors
- Type-casting. For example fields with
booleantype are correctly recognized from the well-known stringified values:'true'/'false'/'1'/'0' - Automatically handles any kind of array types
- Take advantage of having a configuration class:
- Calculate config values based on other config values (e.g.
urlfromhostandport) - Getter functions (e.g.
get debugPort() { return this.port + 1000; }) - Inherit from other configuration classes
- Calculate config values based on other config values (e.g.
- Enforce configuration values to be read-only at compile time (readonly modifier) and at runtime (
enforceReadonlyoption) - Load environment variables from a local file (using dotenv)
:warning: Human-readable error messages
- A required ENV variable is absent:
NoEnvVarError: Variable "BASE_URL" is required for AppConfig.baseUrl - ENV variable value can't be casted to the specified type:
TypeCastingError: AppConfig.debug: Boolean-like value expected, something else is gotten. Raw Value (string): "000" Env Var Name: "DEBUG" Is Array: false - Int expected, a float has been gotten:
AppConfig.logLevel: An Integer number expected, a Float is gotten. Raw Value (string): "11.1" Env Var Name: "LOG_LEVEL" Is Array: false - Int array expected, one item of array is a string:
TypeCastingError: Config.adminUserIDs: An Integer number expected, NaN is gotten. Raw Value (string): "11,22,abc,33" Env Var Name: "ADMIN_USER_IDS" Is Array: true
:books: Documentation
All decorators
- Type Casting property decorators
@EnvInteger()@EnvFloat()@EnvString()@EnvBoolean()@EnvEnum()
- Other
@EnvNested()To create nested configuration objects@Environment()Wraps class and makes.loadEnvConfig()call during instantiating
Class property name & ENV variable name
Usually we write class properties in camelCase notation and environment variable names in SCREAMING_SNAKE_CASE notation. How can we relate them?
- In a simple case
typed-node-envpackage does internal transformation of class property name from came-case to ENV variable name in screaming-snake-case. - You can specify custom ENV name for a field.
@EnvInteger('APP_PORT') public port!: number; - It is possible to specify multiple ENV names for the same property.
In this casetyped-node-envtries to find a first existing ENV variable in the order in which the names listed. @EnvInteger(['PORT', 'APP_PORT', 'HTTP_PORT']) public port!: number; - In case of nested object configured using
@EnvNested()decorator- By default, names of the property that contains a nested object is concatenated to each property name of the nested object.
class DbConfig { @EnvString() public host!: string; // <--- DB_HOST @EnvInteger('PWD') public password!: string; // <--- DB_PWD (custom name is prefixed too) } class Config { @EnvNested() public db!: DbConfig; } - Also, you can customize prefix name
@EnvNested('MY_PREFIX')
- By default, names of the property that contains a nested object is concatenated to each property name of the nested object.
- It even possible to use the same config class for different fields to make complex nested object.
This config is looking for next variables:class DbInsConfig { @EnvString() public host!: string; @EnvString() public login!: string; @EnvString() public password!: string; } class DbConfig { @EnvNested() master!: DbInsConfig; @EnvNested() slave!: DbInsConfig; } @Environment() class Config { @EnvNested() public db!: DbConfig; } const env = new Config();DB_MASTER_HOST # ---> env.db.master.host DB_MASTER_LOGIN # ---> env.db.master.login DB_MASTER_PASSWORD # ---> env.db.master.password DB_SLAVE_HOST # ---> env.db.slave.host DB_SLAVE_LOGIN # ---> env.db.slave.login DB_SLAVE_PASSWORD # ---> env.db.slave.password
Instantiating
- The classic way is using
newkeyword.
To use this way the config class should be decorated with@Environment()decorator.
Notice: Internally during the instantiating of the classEnvironmentdecorator usesloadEnvConfigfunction under the hood.@Environment class EnvConfig { // ... } const env = new EnvConfig(); - Manual
loadEnvConfig()function call.
It can be helpful if by some reasons you don't have to instantiate the config manually usingnewkeyword.loadEnvConfigfunction works with both class constructors and with instances.// No @Environment() decorator here class EnvConfig { // ... } // env1 is a filled instance of EnvConfig const env1 = loadEnvConfig(EnvConfig); const env2Empty = new EnvConfig(); // totally empty object without any fields const env2 = loadEnvConfig(env2Empty); expect(env2Empty).toBe(env2); expect(env2).toEqual(env1); // all fields are equal
.allowEmpty flag
The default behavior is throwing an error about absent ENV variable in case the value of the variable is an empty string or a string filled only with spaces.
If we are decorate fields with any of
@Env*({ allowEmpty: true })decorators such "empty" values will be consumed and passed to type casting function.
Here is a table of values to which such values will be converted by different decorators:| decorator | input value | result | | --- | --- | --- | |
EnvBoolean|' 'or''|false| |EnvFloat|' 'or''|0| |EnvInteger|' 'or''|0| |EnvString|' '|' '| |EnvString|''|''| |EnvEnum|''| throws error except cases when an emptystring is a enum option value | |EnvEnum|' '| throws error except cases when' 'stringis a enum option value |
Handling arrays
Typed Node Env automatically detects array type and splits input ENV variables data by commas (,).
enum ETest {
One = 'one',
Two = 'two',
Three = 'three',
}
@Environment()
class EnvConfig {
@EnvString()
public hosts!: string[] // [ 'my.com', 'your.net' ]
@EnvInteger()
public ports!: number[] // [ 80, 8080 ]
@EnvFloat()
public percentages!: number[] // [ 0.75, 2.3 ]
@EnvBoolean()
public mask!: boolean[] // [ false, false, true, false ]
@EnvEnum(ETest)
public testEnum!: ETest[] // [ 'One', 'Two' ]
}
const env = new EnvConfig();ENV variables
HOSTS=my.com,your.net
PORTS=80,8080
PERCENTAGES=0.75,2.3
MASK=false,false,true,false
TEST_ENUM=One,TwoMixed types
You can define any mixed types for your properties and use multiple @Env*() decorators for the same property.
Use case for TypeORM .logging field handling
/** This is a complex type from TypeORM */
type LoggerOptions = boolean | 'all' | ('query' | 'schema' | 'error' | 'warn' | 'info' | 'log' | 'migration')[];
const allowedValues: LoggerOptions = ['query', 'schema', 'error', 'warn', 'info', 'log', 'migration'];
class EnvConfig {
@EnvBoolean()
@EnvEnum({ enum: allowedValues, isArray: true })
@EnvEnum({ enum: ['all'] })
public logging!: LoggerOptions;
}Various of the ENV variable value:
LOGGING=true # in TS will be transformed to true
LOGGING=all # in TS will be transformed to 'all'
LOGGING=error,warn,info # in TS will be transformed to ['error', 'warn', 'info']More examples
You can find a lot of examples in the typed-env-integration.spec.ts
Similar projects
- https://github.com/igabesz/config-decorators
- https://github.com/jbpionnier/env-decorator
- https://github.com/Hippocrate/env-decorator
- https://github.com/derbenoo/ts-configurable
:wrench: Development notes
Quick Start
cd /code/z-brain
git clone [email protected]:z-brain/typed-node-env.git
cd typed-node-env
yarn installHow to use NodeJS version from the .nvmrc
Install NVM
Use
.nvmrcfile one of the next ways:- Execute
nvm usein the project root directory - Install NVM Loader and your .nvmrc will be loaded automatically when you open the terminal.

- Execute
How to make a build
npm run build
How to run lint
- Just show problems
npm run lint - Fix problems if it is possible
npm run lint:fix
How to run tests
All tests
npm run testnpm run test:watchSpecific tests
npm run test -- src/my.spec.tsnpm run test:watch -- src/my.spec.ts
How to build and publish NPM package
NPM Token: 6cf9...7ab8
CI configuration details are here: .github/workflows/npmpublish.yml
npm run pre-push
&& npm version patch -m 'Update package version version to %s'
&& npm run gen-public-package.json
&& cp README.md dist/
&& npm publish dist --access public
&& git push --no-verify && git push --tags --no-verifyHow to build a package for local installation
yarn run build:local- Then you can install a local package build from path
file:.../typed-node-env/dist.
:man_technologist: Author
| Anton Korniychuk | | :---: |
