Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

tconf

codemariner202ISC3.1.0TypeScript support: included

Heirarchical configuration with environment variable coercion and type validation.

config, configuration, env, typescript, runtypes, 12-factor

readme

tconf


Version Downloads/month License

Adapting 12 factor app configuration to a type checked, application focused world view.

Features

  • Hierarchical configuration - values are merged from multiple sources.
  • Supported file formats: yaml, json, json5
  • Environment specific configuration via NODE_ENV
  • Runtime type validation.
  • Support for modulare configuration.
  • Type coercion of environment variables - string values can be converted to:
    • number
    • boolean
    • Date
    • RegExp
    • Array<number|boolean|Date|RegExp>
  • All values can implicitly be configured by environment variables.

Overview

12 factor app guidelines for configuration promotes "strict separation of config from code" through the use of environment variables. While this is beneficial from a deployment perspective, how this is implemented in many cases falls short of adequately supporting complex configuration within an application.

Typical approaches involve referencing process.env directly, perhaps with additional support through a library like dotenv. These applications often start by working with a flat list of variables.

const {
    DB_HOST,
    DB_USERNAME,
    DB_PASSWORD,
    // ...
} = process.env;

As configuration becomes more complex, this flat structure becomes cumbersome to deal with and to reason about. To combat this, developers will organize their configuration into a hierarchical structure. Having to map from a flat list of env vars into a desired shape, performing type coercion from env var strings, and executing validation is often an exercise left for the developer. For example, a desired end state for your configuration might look like:

api: {
  baseUrl: string
  port?: number
  debugMode?: boolean
  auth: {
    secret: string
  }
}
database: {
  host: string
  username: string
  password: string
  driverOpts?: {
    connectionTimeout?: number
    queryTimeout?: number
  }
}
...

Representing this as a flat list of env vars is not an effective way to work with your configuration. tconf addresses this by allowing authors to specify the desired shape and type of their configuration and performs mapping and coercion from environment variables automatically.

Getting Started

1. install

npm install tconf

2. create config specification (optional)

tconf utilizes runtypes for runtime type checking and as a schema for your config. This represents what you want your config to look like.

// src/config.ts
import { Boolean, Optional, Record, Static, String } from 'runtypes';

const ApiConfig = Record({  
    port: number,
    debug: Optional(Boolean)
})
const DatabaseConfig = Record({
  host: String,
  username: String,
  password: Optional(String)
})

const Config = Record({
    api: ApiConfig,
    database: DatabaseConfig
});
export type Config = Static<typeof Config>;

where the type Config is inferred as:

interface Config {
  api: {
    port: number
    debug?: boolean
  },
  database: {
    host: string
    username: string
    password?: string
  }
}

If you aren't using TypeScript or don't care about having your configuration statically typed, coerced, and validated then you can skip this.

3. map to env var names (optional)

Create a config file that defines a mapping of env vars. tconf provides support for template variables that can be used for env var interpolation (similar to docker compose) and also allows for assigning default values.

# config/env.yaml
api:
  port: ${API_PORT:3000}
database:
  host: ${DB_HOST:"db.domain.com"}
  username: ${DB_USER}
  password: ${DB_PASSWORD}

This is also optional. tconf natively supports configuration mapping from environment variables following a path naming convention. (you can set any configuration value using an environment variable). Use interpolation variables in your config only if you need to map from some specifically named variable that doesn't match your config.

4. load your configuration

// src/config.ts
import { initialize } from 'tconf'

const tconf = initialize({
  // directories containing configuration files
  path: path.join(__dirname, '..', 'config'),
  // the runtypes Config object (optional)
  schema: Config,
  // sources to look for config, in this case the files
  // default.yaml, ${NODE_ENV}.yaml, and env.yaml
  sources: ['default', 'NODE_ENV', 'env'],
})
export default tconf.get();

tconf will import configurations from the defined sources (or a set of defaults) from the specified directories, and merge the values in the order of the specified sources.

5. use it

// src/foo.ts
import config from './config'
import dbConnect from './db'

const conn = await dbConnect(config.database);

6. use in isolated modules

Within larger applications, you may want to isolate certain areas of your code into modules. It may make sense to isolate your configuration to such modules as well.

First, expose your initialized Tconf instance:

// src/config.ts
import { initialize } from 'tconf'

export const tconf = initialize({ // <-- export the instance
    // ...
})
export default tconf.get(); // exports the configuration

Then in your module, register your configuration schema and provide access to your module.

// src/modules/db/config.ts
import {tconf} from '../../config'

const Config = Record({
    uri: String
})

const config = tconf.register('database', Config); // Static<typeof Config>

export default config

The configuration will be sourced the same way, but you'll need to add your configuration under the registered name.

# config/default.yaml
api:
  # //...

database:
  uri: postgresql://host.com:5432/appdb

Documentation

Please see the documentation for more detailed information and capabilities of tconf.

changelog

Change Log

All notable changes to this project will be documented in this file.

[3.1.0]

Features

  • Support default values for environment variable templates.
    • example:
      api:
        port: ${PORT:3000}

Chores

  • Update runtypes to 6.7.0

[3.0.0]

Features

  • Add support for modular configuration.

Breaking Changes

  • The default load function has been removed, though it is still available. Callers should instead use the new initialize function to load configuration and get an instance of Tconf.

    • before:

      import loadConfig from 'tconf';
      
      const config = loadConfig({...})
      
      export default config;
    • after:

      import { initialize } from 'tconf';
      
      // export our tconf instance so modules can register their own configuration
      // (without having to make it global to the entire application)
      export const tconf = initialize({...})
      
      export default tconf.get();

[2.2.2]

Chores

  • Upgrade eslint packages.

Bug Fixes

  • Optional fields now processed correctly when performing env coercion. For example, the following should be able to be mapped correctly from env vars:
    const DbConfig = Record({
      host: String,
      database: Optional(String),
    });
    This was previously failing to recognize the Optional.
  • Fixed an issue with env config merging. Nested properties were being assigned at the wrong path.

[2.2.1]

Chores

  • Add LICENSE file
  • Only include required files in package

[2.2.0]

Chores

  • Clean up code
  • Update README
  • Package updates

Features

  • Add support for coercion of env vars to array
  • Ignore empty files

[2.1.2]

Chores

  • Update dependencies.

[2.1.1]

Bug Fixes

  • Add missing parsers file.

[2.1.0]

Chores

  • Code reorganization
  • README update

    Features

  • Add support for json5
  • Add format config value for 'yml'. This is also used as the file extension. Previously, only '.yaml' was supported. You can now use '.yml' or '.yaml'.