Complete configuration solution for typescript codebases.
- Yaml, env, cli config sources with priority configuration and overriding
- Typed, class-based
- JSON schema generation for yaml autocompletion and validation in editors and IDEs
- Documentation generator for yaml and env variables
- Built on the top of the mature community driven libraries:
- class-validator as validation solution
- class-transformer for type management
- dotenv for envs parsing
@ConfigPrefix('app')
class AppConfig {
// Simple field
@ConfigField({ optional: true })
httpPort?: number;
// Nested config
@ConfigField({ nested: true })
db!: DbConfig;
// Sources overriding. Arrays support
@ConfigField({
arrayOf: 'strings',
yamlKey: 'services.manager.tasks',
cliKey: 'servicesList',
arraySeparator: ':',
})
tasks!: string[];
}
class DbConfig {
@ConfigField()
host!: string;
@ConfigField()
port: number = 3306;
@ConfigField()
autoReconnect = true;
}
This library depends on decorators, so make sure your tsconfig.json includes this settings:
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
Install via npm
npm i @monkee/turbo-config
import { Transform } from 'class-transformer';
import { IsNumber } from 'class-validator';
import { buildConfig, ConfigField } from '@monkee/turbo-config';
class AppConfig {
/*
Every field with this decorator will be parsed as config field.
By default keys will be inferred from property name:
APP_PORT=3000 for env
appPort: 3000 yaml
--appPort=3000 for cli
*/
@ConfigField()
@Min(0)
@Max(65535)
appPort: number = 3000;
/*
NOTE: due reflect-metadata limitation you should specity property type
with default value. Othervise library won't infer type and the magic won't work.
Use ignoreProperties flag with no-inferrable-types eslint rule if necessary
*/
/*
There are some transforms and validations under the hood by default.
parseFloat() and isNumber() validator for numbers for example.
Its possible to disable everyting and handle it manually.
Any features of ClassValidator and ClassTransformer is available.
*/
@ConfigField({
disableDefaultDecorators: true,
})
@Transform(({ value }) => parseFloat(value))
@IsNumber()
redisPort!: number;
/*
You can override any source key
*/
@ConfigField({
envKey: 'SUPER_APP_HOST',
yamlKey: 'super.app.host',
cliKey: 'cli.app.host',
})
appHost!: string;
// Array example. Due to reflection limitation you should specify type
@ConfigField({ arrayOf: 'ints' })
intsArray: number[] = [1, 2, 3];
}
const main = async () => {
const { config } = await buildConfig(AppConfig, {
ymlFiles: ['config.yml', 'override.yml'],
});
console.log(config);
};
import { generateConfigDoc, ConfigPrefix, ConfigField } from '@monkee/turbo-config';
import * as fs from 'fs/promises';
@ConfigPrefix('nest')
class AppConfig {
@ConfigField()
port!: number;
}
const main = async () => {
const { jsonSchema } = await buildConfig(AppConfig);
// save config reference to file
await generateConfigDoc(jsonSchema, {
// you can also use return value of generateConfigDoc to manually write file
writeToFile: 'CONFIG_REFERENCE.md',
});
// write json schema to file
fs.writeFile('config-schema.json', JSON.stringify(jsonSchema, null, 2), 'utf-8');
};
In your yml config file you can include generated json schema. Supported in VSCode and IDEA (Webstorm).
# yaml-language-server: $schema=config-schema.json
nest:
port: 3000
import { buildConfig, ConfigField } from '@monkee/turbo-config';
class Nested {
@ConfigField()
port = 3000;
@ConfigField()
autoReconnect = true;
}
class AppConfig {
@ConfigField({ nested: true })
nested!: Nested;
}
const main = async () => {
const { config } = await buildConfig(AppConfig);
console.log(config.nested);
};
import { buildConfig, ConfigField } from '@monkee/turbo-config';
class Repository {
@ConfigField()
url!: string;
@ConfigField()
token!: string;
}
class AppConfig {
@ConfigField({ arrayOf: Repository })
repositories!: Repository[];
}
const main = async () => {
process.env.REPOSITORIES = 'url=first;token=someToken,url=second;token=secret';
const { config } = await buildConfig(AppConfig);
console.log(config.repositories);
};
// Default build options
{
sourcesPriority: [CONFIG_SOURCE.YAML, CONFIG_SOURCE.ENV, CONFIG_SOURCE.CLI],
throwOnValidationError: true,
throwIfYmlNotExist: false,
throwIfEnvFileNotExist: false,
ymlFiles: [],
envFiles: [],
loadEnvFiles: false,
classValidatorOptions: {
skipMissingProperties: false,
},
classTransformerOptions: {
exposeDefaultValues: true,
},
}
const main = async () => {
// manually handle validation errors
const { config, validationErrors } = await buildConfig(AppConfig, {
throwOnValidationError: false,
});
throw new Error(validationErrors);
};
Vlad Boroda - Initial work - Enity