Configuration of node and webapps made easy
There are tons of modules that allow you to configure your apps, but all of them (or at least the ones I tried) seem to do too much or not enough or are clumsy in use.
So I set out to create a configuration utility which will do exactly what I need and with following goals:
- API symmetry in both node and the browser.
- Extremely easy setup.
- Allows the use of ENV variables.
- Variable interpolation of the configuration data.
- Easy integration with existing build tools and package managers: Grunt, Gulp, Broccoli, Bower et cetera.
Install the module with npm install konfy
var konfy = require('konfy');
konfy.load(function(err, config){
if(err) return console.log('Oh noes!');
console.log(config);
});
This will:
- load a
.env
file with environment variables. - load a
config.json
file with configuration data. - interpolate the configuration data if necessary.
Both files are loaded from the root of your project (unless otherwise configured.)
Install the module with npm install konfy
(or bower install konfy
but be sure to read the section on how to use Konfy with Bower).
Konfy is used in conjunction with browserify and should be used in two phases:
-
pre-browserification: the ENV variables need to be loaded before browserify compiles the client-side code.
var konfy = require('konfy'); konfy.loadEnv();
This loads a
.env file
from the root of your project. -
client source: The actual loading of the configuration data
var konfy = require('konfy'); konfy.load(function(err, config){ if(err) return console.log('Oh noes!'); console.log(config); });
This will load a
config.json
from the root of your application and interpolates its data.
Follow browserify guidelines for adding the compiled js file to your html.
Konfy does a number of things under the hood. Since building webapps can be pretty complex with varying approaches it's good to have a clear understanding of what Konfy does exactly.
Konfy uses dotenv which
loads environment variables from .env into ENV (process.env)
If for example your .env
file contains the following:
THE_ANSWER = 42
Then you can access that value with process.env.THE_ANSWER
, e.g.:
console.log(process.env.THE_ANSWER);
Depending on the value of NODE_ENV
a different dotenv file will be loaded. If NODE_ENV
is set to "production" a file .env.production
will be loaded. This allows you to declare different values depending on the environment your code runs in.
When used with browserify all environment variables are processed using envify, which is a browserify transform that will:
replace your environment variable checks with ordinary strings.
In other words once browserify finishes bundling, all process.env.<VARIABLE>
references will be replaced by the values as defined in the .env
file(s). Only the variables you use (in your client-side code) will be included, so you don't have to worry about secret values leaking through.
Just to be clear and drive home the point: YOU SHOULD NEVER, EVER USE SECRET (ENV) VALUES IN YOUR CLIENT-SIDE CODE, since they won't remain secret.
Next the configuration file is loaded:
- Node: The configuration data is simply
require
'd. This also means that you could use a.js
file instead of.json
. - Browser: The configuration file is loaded with an XMLHttpRequest. There's two reasons why this happens dynamically:
- Browserify (and brfs for instance) can't resolve dynamic file names for
require
's other than those using__dirname
. - This is obviously a matter of preference, but I rather have my configuration data loaded on-the-fly, otherwise any changes in the configuration data will force me to recompile my application. On the roadmap: allowing browserified configuration data.
- Browserify (and brfs for instance) can't resolve dynamic file names for
Once the configuration data is loaded it is processed with underscore-tpl. Which is like:
_.template for objects
I.e. it will translate all ERB-style (or mustache, if so configured) tags in your configuration data to the values you feed it with.
For example:
var configData = {
gangsta : "<%= badass %>"
};
var values = {
badass : "Jules Winfield"
};
console.log( _tpl( configData, values ) );
//outputs: { gangsta: 'Jules Winfield' }
❗ N.B.:
In node the _tpl
function is fed with a consolidation of the values defined in your .env
file and the values you pass to konfy.load
. This allows you to spread the declaration of your configuration values as you see fit.
E.g. using the above example .env
file with :
//file: config.json
{
"answer": "<%= THE_ANSWER %>"
}
Will result in a configuration object with value 42
for key answer
.
To achieve the same in the browser you need to pass the required process.env
values to the config
object of the Konfy configuration object.
E.g.:
//file: main.js
konfy.load({
config: {
theAnswer: process.env.THE_ANSWER
}
});
//file: config.json
{
"answer": "<%= theAnswer %>"
}
This too will result in a configuration object with value 42
for key answer
.
The extra assignment is necessary to force envify
to process the appropriate env
values, i.e. they have to be explicitly declared somewhere.
On the roadmap: avoiding this extra step.
When ready Konfy calls a typical node callback with function(err, data)
signature. (On the roadmap: integration of promises)
Konfy uses an XMLHttpRequest instance to load the configuration file, however it's made JQuery compatible. If you want Konfy to use JQuery instead you can assign it to the $
property.
//use the global $ object
var konfy = require('konfy');
konfy.$ = $;
//or when making jquery available through browserify
var konfy = require('konfy');
konfy.$ = require('jquery');
First loads the environment variables from .env
(if this hasn't happened already and only when run in node) and then loads the config.json
file containing the application configuration data. options
is an optional object allowing the configuration of konfy with:
-
options.$
[Object] Allows overriding/setting the library used to load the configuration file.//use the global $ object konfy.load({ $: $ });
-
options.configFile
[String] default: 'config.json', Allows overriding the default filename and path for theconfig.json
file.konfy.load({ configFile : "config/globals.json" });
-
options.templateSettings
[Object] Allows overriding the template settings of underscore-tplkonfy.load({ templateSettings : { mustache : true } });
-
options.config
[Object] Allows feeding the configuration data with values for variable interpolation.konfy.load({ config : { foo: "foo" } });
Loads the .env
file containing the ENV variable values.
options
is an optional object allowing the configuration of konfy with:
-
options.dotenv
[String] default: '.env', Allows overriding the default filename and path for the.env
file. You should NOT include the NODE_ENV value in the filename, this will automatically by appended.konfy.loadEnv({ dotenv: "env/.env" });
Konfy's only requirement is that the ENV values are loaded before the browserification starts.
E.g. in Grunt
//file: Gruntfile.js
'use strict';
var konfy = require('konfy');
konfy.loadEnv();
module.exports = function(grunt) {
// Show elapsed time at the end
require('time-grunt')(grunt);
// ...
Pro tip:
If you want to use the environment variables in your grunt configuration too just make sure you pass the process.env
object to the grunt configuration object:
//file: Gruntfilejs
grunt.initConfig({
env : process.env
nodeunit: {
files: ['test/**/*_test.js']
},
someTask : {
options : {
foo : '<%= env.FOO %>'
}
}
// ...
Konfy is meant to be used with npm, but a bower.json
file is provided as well. However you will still need the npm version of Konfy for browserification. The Bower version only includes the client-side code of Konfy.
In this case install Konfy as a development dependency through npm:
npm install -D konfy
And use bower to install the client side code:
bower install -S konfy
#file: .env.production
REMOTE_HOST = http://example.com
//file: config/config.json
{
"rest" : {
"root" : "<%= API_URL %>",
"users" : "<%= API_URL %>/users"
}
}
//file: main.js
var app = require('./app')();
var konfy = require('konfy');
konfy.load({
configFile : "config/config.json",
config : {
API_URL : process.env.REMOTE_HOST
}
},function(err, config){
if(err){
return console.log('Oh noes!');
}
app.start(config);
});
//file: build.js
var browserify = require('browserify');
var fs =require('fs');
var konfy = require('konfy');
konfy.loadEnv();
var b = browserify('./app/scripts/main.js');
var output = fs.createWriteStream('app/bundle.js');
b.bundle().pipe(output);
NODE_ENV=production node build.js
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using Grunt.
Copyright (c) 2014 Camille Reynders
Licensed under the MIT license.