Deploying an application requires developers to put thought and consideration into how it is configured. Many apps are deployed in a development environment before being deployed to the production environment. We need to ensure each environment is configured correctly, it could be disastrous if our production application was using our development database, for example.

Environment variables allow us to manage the configuration of our applications separate from our codebase. Separating configurations make it easier for our application to be deployed in different environments.

Large applications tend to have many environment variables. To better manage them we can use the dotenv library, which allows us to load environment variables from a file.

What are Environment Variables?

From programming we know that variables are stored values that can be changed. They're mutable and can vary - hence the name variables.

Environment variables are variables external to our application which reside in the OS or the container of the app is running in. An environment variable is simply a name mapped to a value.

By convention, the name is capitalized e.g. [email protected]. The values are strings.

If you open the terminal or command line application in Linux, Mac OS, or Windows and enter set, you will see a list of all environment variables for your user.

Why use Environment Variables?

Environment variables are excellent for decoupling application configurations. Typically, our applications require many variables to be set in order for them to work. By relying on external configurations, your app can easily be deployed on different environments. These changes are independent of code changes, so they do not require your application to be rebuilt to change.

Data which changes depending on the environment your app is running on should be set as environment variables. Some common examples are:


  • HTTP Port and Address

  • Database, cache, and other storage connection information

  • Location of static files/folders

  • Endpoints of external services
    • For example, on a development environment your app will point to a test API URL, whereas in a production environment your app will point to the live API URL.

Sensitive data like API keys should not be in the source code, or known to persons who do not need access to those external services.

Environment Variables in Node.js

Consider a hello world Node.js application with environment variables for the host and port the app runs on.

Create a new file called hello.js in a workspace of your choosing and add the following:

const http = require('http'); // Read the host address and the port from the environment
const hostname = process.env.HOST; const port = process.env.PORT; // Return JSON regardless of HTTP method or route our web app is reached by
const server = http.createServer((req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'application/json'); res.end({"message": "Hello World"});
}); // Start a TCP server listening for connections on the given port and host
server.listen(port, hostname, () => { console.log(Server running at http://${hostname}:${port}/);
});

Node.js provides a global variable process.env, an object that contains all environment variables available to the user running the application. It is expecting the hostname and the port that the app will run on to be defined by the environment.

You can run this application by entering this command in the terminal, HOST=localhost PORT=3000 node hello.js, granted you have Node.js installed. You will notice the following message on your console:

Server running at http://localhost:3000/ 

Creating and reading environment variables is that easy. Let's say we are writing a micro-service which communicates with a MySQL and Redis instance, we would like to have the connection details available in our environment variables as well.

We might end up with a command like this:

$ DBHOST=localhost DBPORT=3306 DBUSER=test DBPASSWORD=verySecret123!$ DBMAXCONNECTIONS=15 CACHEADDR=localhost CACHEPORT= 6379 HOST=localhost PORT=3000 node hello.js

Developing with multiple environment variables quickly become unwieldy. It would be better if the environment variables we had to configure were stored in one central place to the application, like a file.

The dotenv Library

This library does one simple task: loads environment variables from a .env file into the process.env variable in Node.js. Let's use dotenv for the previous example.

First, we need to install it via npm:

$ npm init # Optional, creates a local package.json that prevents global installs
$ npm install dotenv --save

And then we make one minimal code change, add this line to the top of hello.js:

require('dotenv').config() 

Now in the same directory of your app, create a new file called .env and add the following:

HOST=localhost PORT=3000 

Your environment variables are declared just as if you were entering them in the shell before running the node command. Instead of separating each variable assignment by a space, we separate them by a newline. Now in your shell, if you enter node hello.js, the app will be running on port 3000 on your localhost!

Even if dotenv is used in the application, it is completely optional. If no .env file is found, the library fails silently. You can continue to use environment variables defined outside the file.

Note: There are also many variations of dotenv across other programming languages and frameworks. A very popular guideline for building modern web applications is The Twelve-Factor App. The document was created by many developers who created Software-as-a-Service applications. One of the 12 considerations of a modern, scalable app is configuration, specifically the use of environment variables to store configuration. The popularity of this methodology has helped fuel the creation and popularity of dotenv and similar modules.

Production Usage

Storing your environment variables in a file comes with one golden rule: never commit it to the source code repository. You do not want outsiders gaining access to secrets, like API keys. If you are using dotenv to help manage your environment variables, be sure to include the .env file in your .gitignore or the appropriate blacklist for your version control tool.

If you cannot commit the .env file, then there needs to be some way for a developer to know what environment variables are required to run the software. It is common for developers to list the environment variables necessary to run the program in a README or similar internal documentation.

Some developers create and maintain a .sample-env file in the source code repository. This sample file would list all the environment variables used by the app, for example:

HOST= PORT= 

A developer would then clone the repository, copy the .sample-env file into a new .env file and fill in the values.

If your app is running on a physical machine or a virtual machine (for example, Digital Ocean droplets, Amazon EC2 and Azure Virtual Machines), then you can create a .env while logged into the server and it would run just as it is done on your local machine.

If your app is running on a docker container or a Platform-as-a-Service provider like Heroku or Openshift, then you will be able to configure environment variables without having to use the .env file.

Remember, it fails silently so it would not affect the running of the app if the file is missing.

Conclusion

Environment variables exist outside our application's code, they are available where our application is running. They can be used to decouple our application's configuration from its code, which allows our apps to be easily deployed across different environments.

With Node.js apps, environment variables are available through the process.env global variable. We can set the environment variables before we run the node command, or we can use the dotenv library which allows us to define our environment variables in a .env file.

The .env file should never be in the source code repository.