Using Environment Variables in Node.js for App Configuration and Secrets
Tags: security, devops, tutorial
Published: 2021-05-28 00:49:59

Many developers start with hard-coded config and secrets in either JSON or JavaScript files, and while this works, it poses a security risk as any developer with code access can potentially view secrets such as production database credentials and API keys.

If you're currently configuring your applications like this, don't worry, you're not alone! Most developers eventually realize that hard-coding credentials isn't the way to go, and begin looking for a more secure alternative in the form of a secrets manager.

This is exactly we created [Doppler](https://doppler.com)—giving developers an easy to use secrets manager for Node.js applications that centralizes secrets storage with an easy to use [CLI for injecting secrets as environment variables](https://docs.doppler.com/docs/enclave-installation).

Environment variables are considered the [best way to configure applications](https://12factor.net/config), with the main benefits being:

*   Secrets such as database credentials are not leaked into source code
*   Ability to deploy an application in any environment without code changes

Using environment variables for configuring Node.js (and even JavaScript front-end) apps enables your application to be built and deployed anywhere, whether it's for local development on macOS, a container in a Kubernetes Pod, or modern Node.js hosting environments such as [Vercel](https://vercel.com/docs/environment-variables), [Heroku](https://devcenter.heroku.com/articles/config-vars), [Netlify](https://community.netlify.com/t/support-guide-using-environment-variables-on-netlify-correctly/267), [Cloudflare Workers](https://developers.cloudflare.com/workers/platform/environments#environment-variables), and [AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html).

Specifically, you could use environment variables to:

*   Set the **NODE\_ENV** environment variable to **"development",** enabling debug mode in your development environment
*   Provide environment specific database credentials
*   Supply an API key, e.g. **STRIPE\_API\_KEY**
*   Set the **HOSTNAME** and **PORT** (e.g Heroku dynamically sets the **PORT** environment variable)

**Note:** If you haven't worked much with environment variables on the commandline, check out our [**ultimate guide for using environment variables in Linux and Mac**](https://doppler.com/blog/how-to-set-environment-variables-in-linux-and-mac).

Now that you know why environment variables are the best way to configure Node.js and JavaScript apps, let's explore the syntax for getting, setting, and loading environment variables.

## **How environment variables in Node.js are populated**

When your Node.js application starts or a script is run, a new (child) process is created which inherits the environment variables from the parent process. Node parses the environment variables, creating a **process.env** object where every key and value is a string.

Using an interactive Node terminal, we can inspect the contents of **process.env:**

![](https://assets.website-files.com/5de9972f49103c9dc496402b/605ab92b9b8423f85b51a61f_node-process-env.jpg)

Now you know how environment variables in Node.js are populated, let's learn how to work with them.

## **How to read a Node.js environment variable**

Accessing an environment variable from **process.env** is no different to that of a regular object:

```sh
const PATH = process.env.PATH
```

The one notable difference with the **process.env** object, is that **every key and value will always be a string**. This is because environment variables themselves can only ever be strings.

```sh
process.env.MY_INT = 1000;
console.log(typeof(process.env.MY_INT))

// >> 'string'
```

If a value you're accessing from an environment variable needs to be a non-string type, you must parse or cast it yourself, e.g. casting a  **PORT** environment variable to an integer:

```sh
const PORT = parseInt(process.env.PORT);
```

While the syntax for accessing an environment variable is simple, more thought is required when deciding how environment variables are parsed and used, e.g. how to handle the case when a required variable is not supplied.  

## **Required environment variable with no default**

Because JavaScript won't error when accessing a key that doesn't exist in **process.env**, you'll need to design a solution for how your application will behave when a required environment variable is not supplied.

One option is to use the **ok** method from the **assert** module:

```sh 
const assert = require('assert').strict

const API_KEY = process.env.API_KEY
assert.ok(API_KEY, 'The "API_KEY" environment variable is required')
```

If executed without setting the **API\_KEY** environment variable, you'll be greeted with something like the following:

```sh 
node:assert:399
    throw err;
    ^

AssertionError [ERR_ASSERTION]: The "API_KEY" environment variable is required
    at Object. (/Users/rb/dev/required-api-key.js:3:8)
    at Module._compile (node:internal/modules/cjs/loader:1108:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10)
    at Module.load (node:internal/modules/cjs/loader:973:32)
    at Function.Module._load (node:internal/modules/cjs/loader:813:14)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:12)
    at node:internal/main/run_main_module:17:47 {
    generatedMessage: false,
    code: 'ERR_ASSERTION',
    actual: undefined,
    expected: true,
    operator: '=='
}
```

Why use **assert.ok**? Because we want our application to throw an exception and exit if it's not configured properly.  

You might be thinking "that's kinda ugly" which personally I'm ok with, as it should be a rare exception that your app is incorrectly configured.

If you wanted to handle this case with nicer output, you could use something similar to:

```sh
const API_KEY = process.env.API_KEY

if (!API_KEY) {
    console.error('[error]: The "API_KEY" environment variable is required')
    process.exit(1)
}
```

Now if executed without setting the **API\_KEY** environment variable, the output would be:

```sh
[error]: The "API_KEY" environment variable is required
```   

Whichever you choose is personal preference—the main thing being that you're handling the case of a missing required environment variable strictly and correctly.

## **Required environment variable with a default value**

You can have a default value returned if an environment variable doesn't exist by using the **||** (OR) operator:

```sh
const HOSTNAME = process.env.HOSTNAME || 'localhost'
const PORT = process.env.PORT || 5000
```

So while the code to provide default values is simple, you should **try to avoid defaults in your application code**, the reasons of which I'll cover next.

## **Why to avoid default values for environment variables**

The reason for avoiding defaults for environment variables is simple: A single source of truth should exist for any app config and secret value.

When debugging your application because it is misconfigured (and trust me, it's only a matter of time), the removal of defaults means every config or secret value was supplied by environment variables—that's your source of truth.

Default values in application code can make debugging a misconfigured application more difficult, as the final config values will likely be a combination of hard-coded default values and environment variables.

Make life easier for your future self (and team) by requiring all app config and secret values be set explicitly using environment variables.

## **How to set environment variables in Node.js**

Setting or creating a new environment variable in Node.js is the same as setting a key on a standard object, except Node will implicitly convert any non-string value to a string.

It's recommended to always convert a value you wish to set as an environment variable to a string first, as future versions of Node may throw an exception if a non-string value is assigned.

```sh
process.env.FASTEST_SHIP = 'Millennium Falcon'
process.env.HYPERDRIVE_ACTIVATED = true // This may fail in future so don't do this
process.env.HYPERDRIVE_ACTIVATED = 'true' // Only assign string values
console.log(process.env.HYPERDRIVE_ACTIVATED)

// >> string
```

## Changing environment variables in Node.js

When it comes to changing Node.js environment variables, there are two things to be aware of:

1.  Changes to environment variables in the parent process after the Node.js child process is created are not reflected in the Node.js process
2.  Changes to environment variables in the Node.js process do not affect the parent process

## **How to delete a Node.js environment variable**

Deleting a Node.js environment variable is the same as deleting a key from a standard object:

```sh
delete process.env.USER
```

Deleting an environment variable only affects the current script or application and doesn't affect the parent process.

As an aside, if you're confused as to why deleting a key from **process.env** is always true (even once the key has been deleted), you're not alone.

It's because it will only return **false** if the property is "non-configurable"**,** such as **Math.PI** or a property created as a result of calling **Object.defineProperty()** or **Object.freeze()**.

Check out the [Mozilla docs for the delete operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete) if you'd like to know more.

## **Why to avoid using a .env file for Node.js environment variables**

As an application grows in size and complexity, so does the number of environment variables needed for app config and secrets.

A popular but [problematic and insecure solution](https://doppler.com/blog/goodbye-env-files) is to store the list of environment variables in a .env **file** and use the npm [dotenv package](https://www.npmjs.com/package/dotenv) to parse the **.env** file and populate the **process.env** object.

While **.env** files are simple and easy to get started with, they also cause a new set of problems such as:

*   Running the risk of accidentally committing the **.env** file to source control
*   [Accidentally publicly exposing the .env file](https://trufflesecurity.com/blog/an-s3-bucket-worm-in-the-making-thousands-of-secrets-found-in-open-s3-buckets)
*   Keeping **.env** files in-sync across every local development environment
*   No standardized approach to maintaining environment-specific **.env** files
*   Onboarding a developer by sharing an unencrypted **.env** file with potentially sensitive data via Slack or other public messaging app could pose additional security issues

These are just some of the reasons why we recommend [**moving away from .env files**](https://doppler.com/blog/goodbye-env-files) and using an environment variable manager such as [**Doppler**](https://doppler.com/) instead.

Doppler provides an access-controlled dashboard for managing environment variables in every environment with an [**easy-to-use CLI for accessing config and secrets**](https://docs.doppler.com/docs/enclave-installation) that work for every language, framework, and platform.

![](https://assets.website-files.com/5de9972f49103c9dc496402b/605b1f41129fa9db280cbfd6_03-doppler-project-dev-secrets.jpg)

You can check out our [Mandalorion GIF sample application](https://github.com/DopplerHQ/mandalorion-gifs-vercel) and [deploy it to Vercel](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2FDopplerHQ%2Fmandalorion-gifs-vercel&env=GIPHY_TAG,GIPHY_RATING&envDescription=GIPHY_TAG%20should%20be%20%22mandalorion%22%2C%20GIPHY_TAG%20should%20be%20%22pg13%22%2C%20and%20if%20you%27ve%20created%20a%20GIPHY%20API%20key%2C%20manually%20add%20a%20GIPHY_API_KEY%20environment%20variable&envLink=https%3A%2F%2Fgithub.com%2FDopplerHQ%2Fmandalorion-gifs-vercel%2Fblob%2Fmain%2Fsample.env&project-name=doppler-mandalorion-gifs&repo-name=mandalorion-gifs-vercel&redirect-url=https%3A%2F%2Fdashboard.doppler.com%2F&developer-id=oac_PwsovDZUAMuUygRu6bp4AMme&integration-ids=oac_UdMtwPpcN7yXVQsZFvMSQFy5) to see how to use Doppler for managing environment variables for a Node.js application.

## What is the NODE\_ENV environment variable used for?

The **NODE\_ENV** environment variable originally came the Express web framework and was used to alter internal behavior, such as caching templates and using less verbose logs if **NODE\_ENV** was set to **production**.

It has since become a popular Node.js convention for conditional logic based on the environment type, for example:

```sh
const isDevelopment = (process.env.NODE_ENV === 'development' ? true : false)

if(isDevelopment) {
    // use verbose logging
    // use server in development mode
}
```    

## **Summary**

Awesome work! Now you know how to use environment variables in Node.js for application config and secrets.

Although we're a bit biased, we encourage you to [**try using Doppler**](https://dashboard.doppler.com/register) for managing your Node.js environment variables, and it's free to get started with our [Community plan](http://doppler.com/pricing) (unlimited projects, secrets, and users).

To see Doppler in action, check out the [Mandalorion GIF sample application.](https://github.com/DopplerHQ/mandalorion-gifs-vercel)

I hope you enjoyed the post and if you have any questions or feedback, you can send me an email at [ryan@doppler.com](mailto:ryan@doppler.com), or chat with the Doppler team in our [**Community forum**](https://community.doppler.com/).

---

[![Create your free Doppler account](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lcu1dmvld1wzmz3b0wl0.png)](https://dashboard.doppler.com/register?utm_source=dev&utm_medium=blog&utm_content=using-environment-variables-in-node-js-for-app-configuration-and-secrets-2260)