«

Environment-based configuration for JavaScript applications

As your application grows, so does the possibility that you will require some configuration settings which change depending on where you're running that application, may it be development or production environments.

When writing Node.js applications there are loads of options for what you can use to manage environment-specific configs, my favourite being a mixture of Config & JS-Yaml. However when it comes to client-side JavaScript applications, you're left to discover your own solution without much help.

After being presented with the issue of managing a products frontend configuration, I had to find my solution.

The solution

Note: I'd previously set up the the application to build CommonJS modules using WebPack, if you're looking for help on WebPack, Pete Hunt has a great repository on GitHub to help with that. I'll also be using Gulp to run the tasks required for the configuration setup.

Directory structure

An extremely stripped down root directory structure for the application looks like the following:

+-- build // Folder to hold our pre-build files
+-- configs // A folder to hold per-environment config files
|   +-- production.js
|   +-- development.js
+-- src // Our code, agnostic to the environment
|   +-- app.js

Configuration file

Each configuration file will consist of one thing, exporting an object literal.

module.exports = {

  api : {
    host : 'http://api.host.com'
  }

};

The plan

There are a number of steps to complete the desired process for our configuration setup:

  1. Clean the build folder from any previous-builds files.
  2. Find out which environment we're building for.
  3. Grab the build-environments configuration file.
  4. Rename the file.
  5. Copy the file in to our build folder ready to be built.
    • Also a great time to do any transforming or transpiling.
  6. Copy the rest of our src folder in to the build folder.
  7. Build!

The process defined above will result in a simple way to use our configuration files.

var Config = require('config');

console.log(Config.api.host) // http://api.host.com  

With the build-processes steps outlined, lets get to it. First, we must install the packages required for the process: Gulp, Del & Gulp-Rename.

$ npm install gulp del gulp-rename --save-dev

With those installed, we can start writing some code!

Setting up our Gulpfile

Setting up our Gulpfile consists of two things, requiring our packages and setting up some quick build settings. Once we've got those out of the way, we can start our build process.

var Gulp   = require('gulp');  
var Del    = require('del');  
var Rename = require('gulp-rename');

/**
 * Build Settings
 */
var settings = {

  /*
   * Environment to build our application for
   * - We'll come back to this soon.
   */
  environment  : 'production',

  /*
   * Where is our config folder?
   */
  configFolder : '/js/config',

  /*
   * Where is our code?
   */
  srcFolder    : '/js/src',

  /*
   * Where are we building to?
   */
  buildFolder  : '/js/build',

  /*
   * Where should the final file be?
   */
  destFolder   : '/public/js'

};
Part 1/7 - Cleaning the build folder

Cleaning the build folder is a quick simple task where all the hard work is handled by our Del package.

/**
 * Clean Task
 *
 * Clears the build folder from our 
 * previous builds files.
 */
Gulp.task('clean', function(cb) {  
  Del([
    settings.buildFolder + '/**/*'
  ], cb);
});
Part 2/7 - Find out which environment we're building for

For part two we're going to go back to the settings object and edit the environment variable with the following line.

var settings = {  
  environment : process.env.NODE_ENV || 'production',
};

This line will attempt to find the NODE_ENV environment variable set on the machine, but use production as a backup.

Part 3/7, 4/7 and 5/7 - Find the config file, rename it and copy it to the build folder

Parts three-through-five are all part of the same task, where each part is a simple line. We use Gulp to find the correct-environments config file, rename it and get it ready for building.

/**
 * Config Task
 *
 * Get the configuration file, rename it 
 * and move it to be built.
 */
Gulp.task('config', function() {  
  return Gulp.src(settings.configFolder + '/' + settings.environment + '.js')
             .pipe(Rename('config.js'))
             .pipe(Gulp.desk(settings.buildFolder));
});
Part 6/7 - Copy the rest of our code from src to the build folder
/**
 * Src Task
 *
 * Grabs all the files from the src folder and 
 * places them in our build folder.
 */
Gulp.task('src', function() {  
  return Gulp.src(settings.srcFolder + '/**/*')
             .pipe(Gulp.dest(settings.buildFolder));
});
Part 7/7 - Build everything

This would be the step where you use WebPack, Browserify or any other module system to build your JavaScript modules in to one, or many, sweet file(s).

/**
 * Build Task
 *
 * Build our transpiled/compiled and config 
 * files in to one awesome file.
 */
Gulp.task('build', function() {  
  return Gulp.src(settings.srcFolder + '/app.js')
             .pipe(// Employ your build system)
             .pipe(settings.destFolder);
});

Putting it all together

Great, now we've got an environment-based configuration file building with our source, which we can require like any other module! However, we need to run the tasks in the correct order.

For running tasks in a sequence, I prefer to use the run-sequence package. Using this package results in a clear idea of which tasks will run after the other. It also results in a single place for our sequencing, instead of being dotted around each of the tasks.

Lets install the run-sequence package, require it in our Gulpfile and create our default task to run our other tasks in sequence.

$ npm install run-sequence --save-dev
var RunSequence = require('run-sequence');

/**
 * Default Task
 *
 * Run the above tasks in the correct order
 */
Gulp.task('default', function(cb) {  
  RunSequence([
    'clean',
    'config',
    'src',
    'build'
  ], cb);
});

Optional CLI option to set the environment

In some occasions, we may want to set the environment to build with by passing an option in as we run the task via the command line. For this, we shall use the yargs package and some simple logic.

$ npm install yargs --save-dev
var argv = require('yargs').argv;

var settings = {

  /*
   * Environment to build our application for
   * 
   * If we have passed an environment via a
   * CLI option, then use that. If not attempt
   * to use the NODE_ENV. If not set, use production.
   */
  environment : (!!argv.env
                  ? argv.env
                  : process.env.NODE_ENV || 'production',

  ... // Rest of settings object

};
$ gulp -env development // Run the sequence in development mode

Wrapping up

And just like that, you have an environment based configuration file which builds in to your built file seamlessly and can be required just like a standard module file!

If you have any questions, be sure to find me on Twitter and if you'd like to see the full Gulpfile, you can find it on Github.

Share Comment on Twitter