Managing environment variables in Nodejs and Modern JS apps

Mahmoud Felfel
dubizzle Engineering
4 min readOct 29, 2018
secrets

Techniques to manage your secrets and environment variables through your nodejs and client javascript app.

Every app needs environment variables to define configs and secrets e.g. API_BASE_URL, SOME_SECRET, ...etc.

You can just add them as a JSON or a constants JS file in your app but you need to have these as global configs where you can use from any file without importing, and they might have some secrets and API keys which you shouldn’t add to your source control system.

How to define an environment variable

You can simply define environment variables using any cli like this:

Available to the current shell session only.

MY_SECRET_KEY=iamasecret

This will be available to the current shell and all the processes those start from it.

export MY_SECRET_KEY=iamasecret

And To get it added permanently to all the future bash sessions, add the variable to your ~/.bashrc ( or ~/.zshrc if you are using the fancy OH-MY-ZH) then source ~/.bashrc.

In Nodejs, you can access the global variable process.env to access all the available environment variables to the current node process.

initSecretService(process.env.MY_SECRET_KEY)

Note: you will notice some npm scripts being written like this:

scripts: {
'start': 'cross-env PORT=8080 node server.js'
}

Quick Tip:
cross-env here is not required but it is a good practice to make sure it works on windows. find cross-env package here
https://www.npmjs.com/package/cross-env

That will set the variable PORT to that value 8080 only when running this server session, so if the next time you run it without PORT it will be either undefined or will fallback to the default value.

In many node apps they do it like this when starting the server, which will fallback to a default value if PORT is not defined:

const app = require('http').createServer();
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});

Passing environment variables individually like the above is not practical as your app might need more variables to add over time.

So here is how to do it in Nodejs in a more scalable way:

.env file

In Nodejs you can list your env variables in a .env file then use one of these methods to load the variables and make them available to be used in your app:

1. dotenv

The most common way of doing this is to use the dotenv npm package to load the .env variables when your app is starting.

first, install it

npm install dotenv --save

then add this to your app’s entry file

require('dotenv').load(); // alias of .config()

That will read your .env file, parse the contents, assign it to process.env, and return an Object with a parsed key containing the loaded content or an error key if it failed.

Few tips:

  1. dotenv doesn’t by default overwrite any existing env variables, but if you really need to do it (which is not recommended), well, you can:
// set any process.env directly  
process.env.MY_TOP_SECRET = "123456789";
// or use the parsed key to force all the process.env keys to be set // to the .env ones
const envConfig = dotenv.load().parsed;
for (let k in envConfig) {
process.env[k] = envConfig[k]
}

2. If you’d rather not have require("dotenv").load() in your code, you can use the --requirecommand line option to preload dotenv when you need it.

> node --require dotenv/config server.js

3. Don’t forget to add the .env file to your .gitignore to be sure it won't get pushed to your source control system (i.e. github) with all your secrets by mistake 😱.

4. Usually hosting services like AWS Beanstalk, Heroku, or Now.sh provide their own mechanisms (which you need to configure) to load your env variables in your environment when you deploy your app, so mostly in these cases, you don’t need the .env file on production. And don’t worry if you don’t provide the .env file, dotenv will fail silently.

5. To make .env file more readable in VS code, you can use this plugin.

2. dotenvenc:

Another technique used along with dotenv and is particularly useful if you want to share secrets between different team members (you shouldn't push your .env file to your source control system, again).

In this approach you encrypt the .env file locally then push the generated .env.enc file to your source control system, and you share only the encryption key with your team members.

> npm i -g dotenvenc
> dotenvenc myPassword

Then in your code:

// will only regenerate `.env`; it will not create any environment variables from itrequire('dotenvenc')('myPassword'); // this will read the generated `.env` and populate process.env.* accordinglyrequire('dotenv').config();

For more about this approach check this article by John Resig https://johnresig.com/blog/keeping-passwords-in-source-control/

3. env-cmd:

Instead of using dotenv you can use this package in your npm script to load your environment variables on the fly from the .env file:

{
"scripts": {
"test": "env-cmd ./test/.env mocha -R spec"
}
}

It is particularly useful if you have multiple .env files for different purposes, e.g. one for testing, and another one for production.

Using webpack:

Setting environment variables in your shell doesn’t make them available during compile time.

To achieve that, You can use webpack to load environment variables in your project using the webpackDefinePlugin:

plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
},
}),
],

And If you want to add the same .env variables to webpack config, instead of adding them one by one, you can use dotenv-webpack plugin which wraps both definePlugin and dotenv and while building your code, it checks only for the variables used in your code and makes them available to your build.

// webpack.config.js
const Dotenv = require('dotenv-webpack');

module.exports = {
...
plugins: [
new Dotenv()
]
...
};

That was a quick overview of how to add and use environment variables in your nodejs and javascript projects.
Thanks for reading, and please share your techniques of handling env variables and secrets (if any).

--

--