Managing environment variables in Node.js


Disclaimer: This article exposes what I (and others) feel is the correct way to manage environment variables in development.

Environment variables help us define values which we do not want hard-coded in our source. They allow us to customize code behavior depending on the environment in which it is running.

There is a problem in the way most people manage their environment variables though, and it has to do with their scope.

Let’s say we are building an app which uses Amazon web services’ S3 to upload some assets. Amazon requires us to use a Secret Access Key and an Access Key Id.

The most common way to set environment variables is by declaring them in our .bash_profile file.

Like this:

export AWS_SECRET_ACCESS_KEY=foo
export AWS_ACCESS_KEY_ID=bar

Simple enough, after a quick reload we can access our environment variables in node:

var secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
var accessKeyId = process.env.AWS_ACCESS_KEY_ID;

Good, this usually works well, unless we have, for example, another project which also uses AWS credentials. We can’t name the variables the same because environment variables are global, so all we are left with is namespacing.

After a couple of years using this approach, your .bash_profile can get pretty big. Not only that but you will probably have to think twice before deleting a 2-year-old environment variable (how can you be so sure it is not being used somewhere else?).

The solution: Local environment variables

Yep, environment variables which only exist in the scope of a certain project.

It is common knowledge that values which are sensitive or depend on the code’s environment should not be committed to version control (because of course, you want the environment to provide them), so don’t worry, we won’t be doing that. Well… sort of.

We need the following:

  • A way to read local environment variables
  • A setup script
  • An ignore rule for our environment file

To make this happen we will be using DotEnv.

The setup

We first install the package:

npm install dotenv --save

Now we create a .sample-env file with sample/default values for our development environment:

AWS_SECRET_KEY=fake_secret
AWS_ACCESS_KEY_ID=fake_key_id

The template file serves as a guide for developers to know which environment variables the project needs, and also works for setting default variables when possible.

Now we will create a setup script for our project (if we don’t have one), which any contributor should run before they start working

Here’s mine (setup.js):

'use strict';
fs = require('fs');
fs.createReadStream('.sample-env')
.pipe(fs.createWriteStream('.env'));

We then can run it with

node setup.js

(Or declare it as an npm script)

The script simply copies the contents from the .sample-env and creates with them a new file called .env

By default, the DotEnv library will look for the .env file to set the environment variables.

We now add an ignore rule (if using Git) to avoid the environment variables from being committed to the source code.

Add the following line to your .gitignore file:

.env

Almost ready, now we open our .env file and edit the values with real values

AWS_SECRET_KEY=real_secret
AWS_ACCESS_KEY_ID=real_key_id

Remember to edit the .env file, not the .sample-env

Finally we need to use DotEnv to load our environment variables

var dotenv = require('dotenv');
// There's no need to check if .env exists, dotenv will check this // for you. It will show a small warning which can be disabled when // using this in production.

dotenv.load();

Our development environment is now ready, we have local environment variables which don’t conflict between different projects.

Remember, this is for your Development environment, not Production

It is important to remember that this setup should only be used for development. For production you should still set your environment variables in the standard way (If using Heroku, that would be heroku config:set <variable>=<value>).

That’s it.

Hope this helps you guys as it helped me, feel free to comment your thoughts on this approach.

Special thanks to @sebastianvera who took the time to research into this a while ago at work.