@z-brain/typed-node-env
v1.0.6
Published
π₯ Strictly typed access and type-casting for ENV variables π
Downloads
374
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
boolean
type 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.
url
fromhost
andport
) - 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 (
enforceReadonly
option) - 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-env
package 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-env
tries 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
new
keyword.
To use this way the config class should be decorated with@Environment()
decorator.
Notice: Internally during the instantiating of the classEnvironment
decorator usesloadEnvConfig
function 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 usingnew
keyword.loadEnvConfig
function 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,Two
Mixed 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 install
How to use NodeJS version from the .nvmrc
Install NVM
Use
.nvmrc
file one of the next ways:- Execute
nvm use
in 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 test
npm run test:watch
Specific tests
npm run test -- src/my.spec.ts
npm 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-verify
How 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 | | :---: |