Skip to content

Create a 12 factor compliant configuration system

Simone Primarosa edited this page Sep 24, 2017 · 1 revision

This wiki page aims to provide you with a practical way to implement a config system that satisfies the principles outlined by 12 factor webiste.

As exemple we will setup an app that needs to configure a mongoose instance on startup.

Folder structure

`-- your-app-name/
    |-- src/
    |   |-- configs/
    |   |   |-- envs.js
    |   |   |-- mongoose.js
    |   |   `-- index.js
    |   `-- app.js
    |-- .env
    `-- package.json

.env

This is a file used to overwrite default hard-coded configs.
This demo content replaces the default mongoose URI of our app with an offline one.

your-app-name_mongoose_uri=mongodb://user:password@offline_dev_host:port/database

src/configs/env.js

This file is used internally to load the environment variables contained in the previously mentioned .env file using node-env-file.

It also converts all environment variables into an object with env-dot-prop using a namespace ('your-app-name') to filter unnecessary node defaults env vars.
See this to understand how env-dot-prop parses the env vars.

const path = require('path');

const envDotProp = require('env-dot-prop');
const env = require('node-env-file');

const appRoot = require('app-root-path');
const envPath = path.join(appRoot, '.env'); // The path of the '.env' file

const namespace = 'your-app-name';

// Gets the .env file
try {
  // The file may not exist, so we need a try catch block
  env(envPath, {overwrite: true});
} catch (err) {}

// Parses the environment vars.
const configs = envDotProp.get(namespace, {parse: true, caseSensitive: true});
// => { mongoose: { uri: 'mongodb://user:password@offline_dev_host:port/database' } }

module.exports = configs;

src/configs/mongoose.js

Then we need to create an ad-hoc config file to provided hard-coded defaults.
In your app, you can create one for each package you need to provide configs to.
This structure will help you to keep configs clean and well organized.

const _ = require('lodash');

const envs = require('./envs');  // The file previously created

const defaults = {
  uri: 'mongodb://user:password@online_dev_host:port/database',
  options: {
    // Opt-in for the new connection logic of mongoose 4.11.x
    useMongoClient: true,
    // Good way to make sure mongoose never stops trying to reconnect
    reconnectTries: Number.MAX_VALUE
  }
};

// We overwrite defaults using environment variables.
// And then we export them.
module.exports = _.extend(defaults, envs.mongoose);

src/configs/index.js

This file is used to export all configuration files available in the configs folder.
In this case, there is only the mongose.js file, but ideally here you will add all other config files you will create for your app.

module.exports = {
  mongoose: require('./mongoose')
};

src/app.js

Now you can import configs whenever you need configurations in your app.

const mongoose = require('mongoose');

const configs = require('./configs/'); // Our cool config object

mongoose.connect(configs.mongoose.uri, configs.mongoose.options);