Sometimes it makes sense to have a static parameter in configuration. For example, when connecting to third party service, you want to have api key configurable per environment, but connection options not. You can insert in your configuration schema constant values of any type without loader and use Configuru for application parameters as well.
const configSchema = {
configurable: loader.string('KEY'),
static: 'foo',
}
// { configurable: <loaded value>, static: 'foo' }
const { configurable, static } = values(configSchema)
Hidden variables, secrets and logging
config.ts
import { createLoader, values, maskedValues } from 'configuru'
const loader = createLoader()
// create `buildConfig` function, we will use two loaders
const configSchema = {
// add hidden flag
apiKey: loader.string.hidden('SECRET_KEY'),
}
export default values(configSchema)
export const maskedConfig = maskedValues(configSchema)
foo.ts
import config, { maskedValues } from './config'
// use in your app, never log
config.apiKey // szvor4VYgS79z3QSBtmN0dJeyXbg1Xip
// don't use, is truncated (or hidden for shorter vars) but okay-ish to log
maskedConfig.apiKey // [redacted]
import { createLoader } from 'configuru'
const loader = createLoader({
defaultConfigPath: 'default-config', // defaults to ".env"
userConfigPath: process.env.USER_CONFIG, // defaults to process.env.CFG_JSON_PATH
envMode: 'all', // defaults to "default"
})
defaultConfigPath
: Where to look for your default config JSON file (provide null to skip)userConfigPath
: Where to look for your user config JSON file (provide null to skip)envMode
: How to handle process.env variablesall
- Load (override) all vars available in process.env to the storedefault
- Load (override) only vars with keys from default configmerged
- Load (override) only vars with keys from either (user or default) confignone
- Don't use env variables
🦉 When configuring configuru, you can always use paths with or without extension, it will try to find any of the supported formats via replacing/adding valid extensions.
Configuru is type safe, but you can run into problems, having an enum value parsed as a string. Since there is no enum loader, thus no validation, the value can be anything.
const loader = createLoader()
type Level = 'info' | 'warn' | 'unknown'
const createLogger = (opts: { defaultLevel: Level }) => {
/*...*/
}
const config1 = values({
defaultLevel: loader.string('LOGGER_DEFAULT_LEVEL'),
}) // type -> { defaultLevel: string }
createLogger(config1) // error: string not assignable to Level
To solve it, you can create a custom loader
// create custom loader
const levelLoader = loader.custom(x => {
let level: Level = 'unknown'
if (x === 'info' || x === 'warn') {
level = x
}
return level
})
// or just
const optimisticLevelLoader = loader.custom(x => x as Level)
const config2 = values({
defaultLevel: levelLoader('LOGGER_DEFAULT_LEVEL'),
}) // type -> { defaultLevel: Level }
createLogger(config2) // OK
Default loaders not enough? You can use custom loaders to create any transformation you want and types will be inferred from the return type of you function.
// 'PHOTO-2019-04-01' => { type: 'PHOTO', date: Date }
const stampLoader = loader.custom(x => {
const [type, y, m, d] = x.split('-')
return { type, date: new Date(y, m, d) }
})
const config = values({
stamp: stampLoader('STAMP'),
}) // type -> { stamp: { type: any, date: Date } };
Custom loaders can also return an object that contains nested loaders. Meaning you can have a custom loader that loads a value from storage and based on that value returns another objects (or an array of objects) that themselves may contain loaders.
This can be particularly useful for arrays of connection strings that may contain sensitive information like passwords. This will give you type-safe array of objects, while still supporting hidden loaders.
// env.json
// {
// "PASSWORD": "pwd",
// "HOSTS": "host1,host2"
// }
const schema = {
connections: loader.custom(x => {
return x.split(',').map(host => ({
host,
password: loader.string.hidden('PASSWORD'),
}))
})('HOSTS'),
}
console.log(maskedValues(schema))
// [
// {
// "host": "host1",
// "foo": "[redacted]"
// },
// {
// "host": "host2",
// "foo": "[redacted]"
// }
// ]