Defining and exposing environment variables with Hem for use in a SpineJS app.

23/11/2013, About a minutes read.

I like defining environment variables - they allow me to keep my code clean and tidy when it comes to using things like API keys & secrets, usernames, passwords, connection strings etc. So where possible i use environment variables to extract the repetitive use of these. The aspect of my job that this helps with the most is when things change, later down the line. I don’t have that nervous job of making sure i’ve updated API keys and secrets in every place within my app; a couple of changes to the environment variables and we’re good to go.

Doing this in Ruby, locally with a .env file and on Heroku too, is really easy: https://devcenter.heroku.com/articles/config-vars

However, i wasn’t so sure how to achieve the same results with our Spine app. Fortunately, Michael Bleigh came to the rescue with some useful knowledge.

Defining Environment Variables for a SpineJS App

Disclaimer: This post is based largely off of the expertise of Michael Bleigh, CEO of Divshot. Thanks for pointing me in the right direction on this one Michael: ‘Exposing Environment Variables to Static Spine.js Apps’

Using, Hem, we can write our own custom compilers. Michael shows how to write a compiler that will consume a .env that contains a JSON hash and expose the data.

I started by creating a slug.js file in my project’s root:

    var hem = new (require('hem'));
    var fs   = require('fs');
    var argv = process.argv.slice(2);

    hem.compilers.env = function(path) {
      var content = fs.readFileSync(path, 'utf8');
      var envHash = JSON.parse(content);

      for (key in envHash) {
        if (process.env[key]) {
          envHash[key] = process.env[key];
        }
      }

      return "module.exports = " + JSON.stringify(envHash);
    };

    hem.exec(argv[0]);
    

Within the ‘app’ folder of my project i then created an environment.env file with a JSON hash of my values:

    {
      "RUNTIME":"web",
      "FB_APP_ID":"MY_FACEBOOK_APP_ID",
      "FB_APP_SECRET":"MY_FACEBOOK_APP_SECRET",
      "FB_CHANNEL_FILE":"//dev.pingle.co.uk:9294/channel.html",
      "GA_ID":"MY_GOOGLE_ANALYTICS_ID"
    }
    

These were the default values that my app would load in when served up.

$ hem server

And any one of these values could be overwritten very simply, for example, on compilation of the project…

$ RUNTIME=staging hem build

This was a great feature because it meant that i’d be able to maintain Staging versions of my architecture to test against, prior to deploy.

Expose the variables

In order to access the variables, we’d need to drop a little more code into our project:

    window.env = require("environment");
    

Using the exposed variables was then simply a case of using them wherever i needed them in my code, like this for example, to initialize a connection to the Facebook API:

    FB.init({ 
      appId: env.FB_APP_ID, 
      nativeInterface: CDV.FB, 
      useCachedDialogs: false 
    });
    

Don’t forget to preceed your variable with the env element inorder to access it i.e env.FB_APP_ID.

Further Config Values

With this in mind, i created a config.coffee file within /app/controllers/ that further contained some important values i’d use throughout my project, mainly my API connection URL.

    if env.RUNTIME is "staging"
      Config =
        API_HOST: "http://some-staging-api-location/api/v1"
    else if env.RUNTIME is "production"
      Config =
        API_HOST: "http://some-production-api-location/api/v1"
    else
      Config =
        API_HOST: "http://dev-api-location/api/v1"

    Config['version'] = '0.1'
    module.exports = Config
    

Notice how the above file makes use of the environment variable env.RUNTIME that we defined and exposed inorder to define our API connection string.

Using our config variables

In order to use these new settings, i need to require the file where i am about to use it, like so…

    Config  = require('controllers/config')
    

And then simply call the variables, as you’d expect. For example…

      $.ajax
        type: "GET"
        dataType: "json"
        url: Config.API_HOST + "/login"