Working in industries that security was top priority and working with friends and colleagues who were always keeping security in mind; writing a configuration parser that allowed for different types of input seemed like something of necessity.

Recently I decided it would be good to have a script I could quickly copy over to another project and edit as needed with a base template in mind and heres a small example

// config.js
'use strict'
var configure = function() {
  try {
    var cfg = require('../config.json');
  } catch(err) {
    var cfg = {};
  }

  return {
    app:
    { port: process.env.APP_PORT || ((cfg.app || {}).port) || 8000
    , namespace: process.env.APP_NAMESPACE || ((cfg.app || {}).namespace) || '/api'
    , host: process.env.APP_HOST || ((cfg.app || {}).host) || 'localhost'
    }
  , mongo:
    { host: process.env.MONGO_HOST || ((cfg.mongo || {}).host) || 'localhost'
    , user: process.env.MONGO_USER || ((cfg.mongo || {}).user) || 'root'
    , pass: process.env.MONGO_PASS || ((cfg.mongo || {}).pass) || ''
    , port: process.env.MONGO_PORT || ((cfg.mongo || {}).port) || 3336
    , database: process.env.MONGO_DATABASE || ((cfg.mongo || {}).database) || ''
    }
  , redis:
    { host: process.env.REDIS_HOST || ((cfg.redis || {}).host) || 'localhost'
    }
  };
}

module.exports = configure;

Running this script pulls information from a few different possible places. (In order of heirarchy)

  • Environment Variables
  • JSON config
  • Default value

This allows for a few different use cases. In the case of a production usage, a config can be pulled directly from pre-set environment variables. This also allows for a dev to store a copy of a development config that won’t be stored in source control. On a system with a fresh install (potentaly a Docker or Vagrant dev environment) you can set default values that will be used if an ENV var or config file/option is not found.

Breakdown

'use strict'
var configure = function() {
  try {
    var cfg = require('../config.json');
  } catch(err) {
    var cfg = {};
  }

  return {
    app:
    { port: process.env.APP_PORT || ((cfg.app || {}).port) || 8000
    }
  };
}
module.exports = configure;

We’re setting the environment into strict mode and creating a named function

'use strict'
var configure = function() {
    // Code Here
}

// Export the function to be called by index
module.exports = configure;

Within that function first we attempt to pull in the config.json. (I normally store my config.js in a lib folder and config.json in the project root)

try {
	var cfg = require('../config.json');
} catch(err) {
	var cfg = {};
}

This attempts to pull in the config.json and set it to cfg. If the file is not found it create an object literal

From here we parse the ENV and config.json. If neither are found the default is used and sent stored within the object.

Working Inside Out

Within the second example config we’re looking to set the apps port. We need to check to see if the ENV var is set, if that is not set we then go to the config, but there’s a catch. If the config does not exist, we need to fake the existance of any nesting necessary to access that value. When I create my configs, I have use a few different sections depending on different aspects of the application. Core application configurations live within the app sub-object and normally use the same very limited configs. For scaling though, we can add addition application specific items in this app object. Some sort of database (this may change depending on the project of course), followed by thrid party resources (API keys).

Faking Subobjects

Since in this example i set cfg = {}, when the config is not pulled in we get to skip a step. Otherwise we would have to start with the following code within each query (cfg || {})

This will check for the cnf.app to exist if it doesn’t it will create an object literal {}. The reason we are setting this to undefined is that if the ENV variable is not set it’s going to check this next, if this does not exist we need to be able proceed to the default setting.

Set Your Heirarchy

For this specific setup I want my ENV variables to be my highest choice, followed by anything within my config.json and when all else fails we need to set the default. So we add a ( around the front of this ternary and at the end add || 8000)

(process.env.APP_PORT || ((cfg.app || {}).port) || 8000)

Which when returned as default would return this JSON object

console.log(configure());

Now to put it into action

Above we have our config. Now lets make an express app that uses that conf to set the port

(function(){
  'use strict'
  require('express-namespace');
  var app = require('express')();
	var config = require('./lib/config')();

app.namespace(config.app.namespace, function() {
  app.get('/', function(req, res) {
      res.send('Hello World!');
  });
});

  app.listen(config.app.port, function() {
		console.log('Listening on %d, at %s', config.app.port, config.app.namespace);
  });
})();

When run, if no config or environment variables are present, this should log to the screen

$ node index.js
Listening on 8000 at /api