Managing Multi-Environment Configurations in Node.js

If you ever struggled with managing your project configuration across multiple environments, then this article is for you.

While working with projects that can run on different environments (e.g., local, develop, staging, production), a typical pattern is creating configuration files per environment. For example, a project having three environments such as local, develop and production can have three configuration files:

.
├── config.local.json
├── config.dev.json
└── config.prod.json

Each of these files defines an object with a similar structure but different configuration values for every environment. For example, the config.local.json might define the hosts for the local API server and the local database connection string:

{
    "apiHost": "https://localhost:8080",
    "mongoUri": "mongodb://localhost:27020/app",
    "logLevel": "debug"
}

Then, a config.js module can use a simple logic that loads the correct configuration file for the target environment, for example, by checking an environment variable such as NODE_ENV:

// config.js

import fs from 'fs';

function getConfigFileForEnvironment(env) {
    switch (env) {
        case 'local':
            return 'config.local.json';
        case 'development':
            return 'config.dev.json';
        case 'production':
            return 'config.prod.json';
    }
}

const configFilePath = getConfigFileForEnvironment(process.env.NODE_ENV);
const rawData = fs.readFileSync(configFilePath);
const config = JSON.parse(rawData);

export default config;

A CI/CD service can be configured to deploy this project for each environment using the NODE_ENV environment variable.

While this setup does its job, it has some drawbacks:

  • No validation for missing keys between environments.
  • Risk of having out-of-sync configuration files.
  • No way to define default values.
  • Difficulty seeing all the configuration values per environment per key in one place.
  • Difficulty creating new environments

Solution - single-config

Single-Config is a small npm package that solves the problems described above. It lets you define a single configuration file for all environments. It can also generate typed configuration using TypeScript.

Continuing the previous example, you can define a single configuration file config.json:

{
    "_envs": ["local", "dev", "prod"],
    "apiHost": {
        "local": "https://localhost:8080",
        "dev":  "https://dev.api.example.com",
        "prod":  "https://api.example.com"
    },
    "mongoUri": {
        "local": "mongodb://localhost:27020/app",
        "dev": "mongodb://dev.mongo.example.com:27017/app",
        "prod": "mongodb://mongo.example.com:27017/app"
    },
    "logLevel": {
        "default": "info",
        "local": "debug"
    }
}

After installing single-config and running its buildconfig command:

npm i single-config -g
buildconfig --env=local

It will generate a config.js module exporting an object with all the properties resolved to the “local” environment:

// This file was automatically generated at 2021-07-18T15:46:04.304Z
module.exports = {
    "env": "local",
    "apiHost": "https://localhost:8080",
    "mongoUri": "mongodb://localhost:27020/app",
    "logLevel": "debug"
};

single-config diagram


comments powered by Disqus