Node.js Configuration Management

Alex Cherednichenko

The first article in a series on Node.js. It details the solutions for typical problems in a classical REST API type application. The idea of tech blog at Logicify has grown from internal talks on this technology; and so did the concept of 'sample' tasks. We use all these tools to help our juniors improve their tech level, so why not share this with the community?

As a lasting example, we are (and will be) using the https://github.com/Logicify/nodejs-sample-task application, which is the on-going sample.

Configuration. The Problem

There are a couple of issues posed by the real world to devs (probably devops as well?), which relate tightly to configuration. To name just a few:

  1. How do we share configuration between developers?
  2. How do we make variable things really variable? E.g. I'd expect there's the same database name on dev machines, but the password to RDBMS is different.
  3. Secret things should be secret. If we are storing configs under source control, we don't want uber-secret passwords to the production database.
  4. How do we switch configs easily? (There's an issue on staging, and I'd like to run the same config on my laptop to test against it!).
  5. How do we manage this efficiently? E.g. we don't want 10 files to be changed and 20 env variables set to be able to run the app.
  6. Config system should be good for different usages, from developers to deep operations.

Quite a bunch of open questions. That's definitely not a full list of issues one needs to deal with in real life.

Hosting Platforms

When we talk about Node.js, one of the probable ways to run it efficiently is cloud. Since node is small, stateless by its nature, scalable etc, running it on the cloud makes it easily scalable. We use Heroku hosting since it is quick to manage, easy to operate, and provides very convenient single billing point for pay-as-you-go deployments.

At the same time, Heroku poses a number of limitations (so do other cloud hosters, I believe), like you only may bind one port, which is specified by Heroku, you can't use the filesystem as persistent storage, etc.

By its nature, this is really different from development environment you usually host on your machine.

Configuration. Node Way

The very nice express library we use already has some basic means of configuration. Consider this (this.app is the Express app):

    this.app.configure("development", "production", function () {
    //mount secure point
    //it forces all request to be redirected on HTTPS
    self.app.use(function (req, res, next) {
        if (!config.isHTTPS(req)) {
            var host = (config.https && config.https.port)
                    ? [config.host, ':', config.https.port] : [config.host];
            return res.redirect(['https://'].concat(host, [req.url]).join(''));
        }
        next();
    });

There is a method app.configure, which takes the config name. In turn, this config name is coming from the well-known NODE_ENV environment variable. In simpler words, I could just run the app like

$> NODE_ENV=dev node application.js

It starts up as a dev env. If I use

$> NODE_ENV=production node application.js

That is the prod then! It's all up to us what to do in each case. Quite basic things to mention is restricting the error report detail level.

Heroku. Environment Variables

Heroku has their own approach to managing the configuration. Using Heroku-toolbelt, one may setup a list of environment variables, which will be passed to the execution context of the application. Since there are many small nodes in Heroku, which may be short-lived, that is an excellent way of making sure they share the same config parameters.

So, you simply setup them from command line, and then these environment variables are passed to the executing app. here is an example:

    > heroku config
    === nodejs-sample-logicify Config Vars
    BONSAI_URL: http://SECRET_URL_NO_I_WONT_TELL_THIS
    MONGOLAB_URI: mongodb://ALSO_A_SECRET_URL
    PAPERTRAIL_API_TOKEN: I_WONT_TELL_YOU_THIS_NO_WAY
    PATH: bin:node_modules/.bin:/usr/local/bin:/usr/bin:/bin

Marrying the Two Approaches

So, what do we have here?

  1. Heroku is nice in managing the configuration via environment variables. Env vars are good since we guarantee security - no one else could see it!
  2. At the same time, configs are better stored as JSON files - there's a structure, you can provide easy defaults, your IDE suggest you what's the key, etc.
  3. Moreover, you can move not only the strings to the config file but also some (minor) behavioral variability.

The approach we have tested and tried:

Files

The actual configuration (structure of keys) is stored in JS files as Javascript objects. JSON file is a bit worse since it forbids certain manipulations and comments. One file per configuration. The files are stored in single place (say, directory named config in the root of the application). Being a Node.js module, when required, the config file gives you the actual configuration object.

Referring to the sample application, you could take a look at configuration folder for samples. Good thing is that configs are organized in tree structure, e.g. you collect everything related to AWS in AWS node.

Sample:

    var configuration = {
    port: process.env.PORT || defaultPort,
    host: 'localhost',
    mongo: {
        dbName: "nodejs-mongo-sample",
        host: "localhost",
        port: 27017
    },
    ...

Loading and Dispatching

From the code using configuration, the entry point is clean and clear. So, in code needing config, you just do:

config = require('./configuration').getConfiguration();
LOG(['Application started on ULR http://', config.host, ':', config.port].join(''));

Conclusion

Having configuration management property setup can simplify portability and repeatability of builds. It also helps to run similar code in different environments.

Related articles

Tags