Environment Variables in Node.js

Many apps need configuration to connect to a database or send requests to error reporting or analytics services. When you’re developing your app on your laptop, you might connect to a database at localhost. When you deploy your app to a hosting provider, you’ll likely want to connect somewhere else.

let databaseHost = "localhost";
if (!isLocal) {
  databaseHost = "example.dblayer.com";
}

There are a couple problems with this approach. First, you’ll need to keep adding specific cases for every environment (e.g. qa, staging, sams-staging). Second, all of your sensitive data like database passwords are in plain text in version control being distributed to everydeveloper on your team, every CI build, and every deploy to every environment. Those maintenance and security headaches can be avoided by storing configuration in the environment.

const databaseHost = process.env.DB_HOST;

How do environment variables work in Node.js?

In Node.js, environment variables are made available through the global process.env object . The object is initially populated from your shell environment (e.g. $USER, $HOME , $PATH).

❯ echo $USER  
max
❯ node -p "process.env.USER"              
max

You can define custom environment variables in your shell and they will appear in process.env.

❯ FOO=bar node -p "process.env.FOO"
bar

If connecting to your database requires a host, port, user, password, and database name, this quickly becomes unwieldy.

❯ DB_HOST=localhost DB_PORT=5432 DB_USER=max DB_PASS=s3cr3t DB_NAME=example node my-server.js

Managing Environment Variables

With dotenv, you can define all of your variables in a .envfile.

DB_HOST=localhost
USER=sam

Then, dotenvwill assign new keys to process.env and leave existing values untouched.

require("dotenv").config();
console.log(process.env.USER); // => max
console.log(process.env.DB_HOST); // => localhost

Overwriting can be dangerous

Overriding existing environment variables can have unintended consequences for your code and its dependencies which is why dotenv does not modify them. This simple example demonstrates how you can innocently corrupt your environment (nodewill not be found because it is not in $PATH).

const cp = require("child_process");
process.env.PATH = "innocent mistake";
cp.execSync("node --version", { encoding: 'utf8' });

If you’d like to overwrite variables anyways, you can still leverage the parsing dotenv performs and manually assign values.

const dotenv = require("dotenv");
const { parsed } = dotenv.config();
process.env.MY_OVERRIDE = parsed.MY_OVERRIDE;

Preloading

Some hosting providers like Heroku, ZEIT Now, and AWS Elastic Beanstalk will populate your environment with variables you configure so you don’t need a .env file. When a .env file is not found, dotenv quietly fails. There’s a minimal startup cost to your app, but if you’d rather not have require("dotenv").config() in your code, you can use the --requirecommand line option to preload dotenv when you need it.

$ node --require dotenv/config my-server.js

Debugging

When process.env isn’t populated as expected, I’ve found it’s usually due to the .env file not being found or making assumptions in how your values will be treated.

Inspecting Errors

If an error occurs during configuration, dotenv returns that error so you can inspect it.

const dotenv = require("dotenv");
const { error } = dotenv.config();
if (error) {
  throw error
}

The most common error is not being able to read the configuration file. By default, the .env file is assumed to be in whatever directory your process starts (usually the root of your project). You can always customize the path via options and that usually solves the disconnect.

require("dotenv").config({ path: "/Users/max/my-project/.env" });

Avoid Boolean Logic

Assigning a property on process.envwill implicitly convert the value to a string.

Even if your .env file defines a variable like SHOULD_SEND=false or SHOULD_SEND=0, the values will be converted to strings (“false” and “0” respectively) and not interpreted as booleans.

if (process.env.SHOULD_SEND) {
  mailer.send();
} else {
  console.log("this won't be reached with values like false and 0");
}

Instead, you should make explicit checks. I’ve found depending on the environment name goes a long way.

db.connect({
  debug: process.env.NODE_ENV === 'development'
});

If something else has you stumped, please open an issue on GitHub.

At the time of writing, dotenv is being downloaded from npm almost 2 million times per month! It’s encouraging to see that many people not keeping their sensitive data in source control. Using environment variables also makes your code easier to share. dotenv is an admittedly simple approach to environment variables (just simple strings). If you’d like more features, check out all of the modules that extend it.

Originally posted on Medium. As of September 14th, 2018, dotenv is being downloaded from npm over 2 million times per week!

Environment Variables in Node.js

Leave your thoughts

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s