Confab: Simple Node.js Configurations

Node.js service configurations run the gamut from sophisticated libraries to ad-hoc lookups of environment variables or arbitrary JSON on the file system. Somewhere in the middle lies confab, a tiny utility for building configurations that are simple, external, and utterly predictable.

$ npm install confab

The core concept is dead simple: a single configuration object is constructed, extended, and validated using a sequence of transformations. With no transformations supplied, the configuration is simply an empty object:

// config.js
module.exports = confab([]); // {}

That isn’t so useful, but confab also ships with a few built-in transformations that can:

  • load configuration data from JSON
  • merge environment variables
  • specify defaults and required fields
  • assign programmatic overrides
  • freeze the results against futher modification.

To set a default port and allow the environment to override it, we can extend our empty configuration with a few transformations:

var config = confab([
  confab.loadEnvironment({ 'PORT': 'port' }),
  confab.defaults({ port: 3000 }),
  confab.freeze()
]); // { "port": 3000 }

Custom Transformations

We can also easily define new tranformations; if we want all applications to report certain configuration values at startup, for instance, we can whitelist and log the interesting parts:

function logConfigValue (whiteList) {
  return function (config) {
    var output = {};
    whitelist.forEach(function (key) {
      if (config.hasOwnProperty(key)) {
        output[key] = config[key];
      }
    });
    console.log(output);
  };
}

The custom transformation can now be used just like the built-ins:

var config = confab([
  confab.loadEnvironment({ 'PORT': 'port' }),
  confab.defaults({ port: 3000 }),
  confab.freeze()
  logConfigValue('port') // log { "port": "3000" }
]);

Portability

Besides encouraging simple, declarative configuration, confab also provides a platform for applying consistent rules across multiple projects. Once our transformation list is settled, we can package the configuration logic up and publish it to a (private) package repository. Maybe we want to require all projects to respect JSON config files stored in a “global” directory:

module.exports = function myConfig (projectName) {

  var env = process.env('NODE_ENV') || 'default';
  var homeDir = process.env('USERPROFILE') || process.env('HOME');

  return confab([
    confab.loadJSON([
      path.resolve(__dirname, 'config.json'),
      path.resolve(homeDir, '.org-config', projectName + '.' + env + '.json'
    ]),
    confab.loadEnvironment({ 'PORT': 'port' }),
    confab.defaults({ port: 3000 }),
    confab.freeze(),
    logConfigValue(['port'])
  ]);
};

To reuse this configuration, we can simply install it:

$ npm install my-config

…and pass it project-specific options as needed.

// app-config.js
module.exports = require('my-config')('my-app');

Conclusion

That’s about it: simple, reusable configuration logic. It’s been invaluable to me for simplifying configuration management and runtime visibility across projects, scheduled tasks, and services, and now it’s open-sourced for your development convenience.

Happy Hacking!

Let’s keep in touch

Get noise-free updates on software, product, and process.

Hey, I'm RJ: digital entomologist and intermittent micropoet, writing from the beautiful Rose City.