July 13, 2021
Customizing Node.js .env files

Applications are commonly required to deploy to many different environments, including staging, testing, and production, without building environment-specific artifacts.

Methodologies like the 12 Factor App specify that an application’s code and configuration are separate but combine during deployment to accommodate specific environments.

Environment variables are one of the preferred methods to define and consume environment-specific configuration values because they are supported by all major operating systems.

They can be defined in most cloud-provider Platform as a Service (PaaS) offerings, and are a common method for configuring platforms, like Docker.

However, it is sometimes convenient to define application settings in a local configuration file instead.

For example, when developing and debugging applications locally, especially when working across many feature branches, the ability to define a set of environment variables in a file streamlines a developer’s experience.

.env files provide a popular solution for defining environment variables, particularly in Node.js. Platforms like Heroku, for instance, use .env files as part of their recommended best practices.

In this post, we’ll look at a simple Node.js application configured via environment variables, and explore how to customize an .env file with new environment variables that override default values.

We’ll then demonstrate how to use multiple customized .env files to quickly switch between multiple environment-specific configurations.

Prerequisites

The sample application we’ll be working through is available on GitHub. To run the application locally, ensure Node.js is installed.

Alternatively, you can run the application in CodeSandBox. Click the link to open the GitHub
source code in a new sandbox to build, run, and expose via a random URL.

The sample Node.js application

The following code shows a simple Node.js web server configured with two environment variables: PORT, which defines the port the web server listens on, and MYNAME, which defines the name returned in the HTTP response:

const http = require(‘http’);
const port = parseInt(process.env.PORT, 10) || 5000;
const name = process.env.MYNAME || “Matthew”
http.createServer((request, response) => {
response.writeHead(200, {
‘Content-Type’: ‘text/plain’
});
response.write(‘Hello, ‘ + name + ‘!’);
response.end();
}).listen(port);

Start by importing the http module:

const http = require(‘http’);

We can attempt to parse an integer value from the PORT environment variable, or default to Port 5000 if the environment variable is not defined or contains something other than an integer:

const port = parseInt(process.env.PORT, 10) || 5000;

The name returned in the HTTP response can be found in the MYNAME environment variable or defaults to the string Matthew:

const name = process.env.MYNAME || “Matthew”

With a web server started, it listens to the port we just defined:

http.createServer((request, response) => {
// …
}).listen(port);

The HTTP response is constructed in the function passed to the createServer() function. Here, we return the name defined above:

response.writeHead(200, {
‘Content-Type’: ‘text/plain’
});
response.write(‘Hello, ‘ + name + ‘!’);
response.end();

Now, we can run the application with the following:

node index.js

Open http://localhost:5000 to see the Hello, Matthew! response, which means our Node.js application is running correctly.

Why do we need to override environment variables?

Why go through the trouble of overriding values, like port numbers, with environment variables in the first place?

These values can already pass in as command line arguments or load from JSON or YAML configuration files, providing more flexibility than environment variables exposing a simple name and value pairs.

However, because reading values from environment variables are often required by PaaS solutions, the PORT environment variable became a de facto standard for defining which port an application listens to.

For example, Heroku, AWS, Azure, and Google Cloud all require Node.js applications deployed to their services to listen to the port defined by the PORT environment variable.

It’s also often required to use environment variables explicitly defined in methodologies like the Twelve-Factor App methodology.

In the post’s introduction, we noted that this methodology requires defining an application’s configuration outside the code; the Twelve-Factor App config section goes into more detail:

The twelve-factor app stores config in environment variables (often shortened to env vars or env). Env vars are easy to change between deploys without changing any code; unlike config files, there is little chance of them being checked into the code repo accidentally; and unlike custom config files, or other config mechanisms such as Java System Properties, they are a language- and OS-agnostic standard. – “The Twelve-Factor App,” Adam Wiggins

By configuring our application via environment variables, we can be sure our code is easy to deploy and customize across a wide range of platforms.

Setting environment variables in Node.js

A Node.js application deployed to a PaaS solution must typically assume the app is listening to a port at a random number. We can demonstrate this locally by setting the PORT environment variable to a value other than 5000.

We’ll also set the MYNAME environment variable to demonstrate how to define an application-specific configuration.

In Linux and macOS, we can define these environment variables like this:

PORT=5001 MYNAME=Jane node src/index.js

In Windows PowerShell, we can define the environment variables like the following:

$env:PORT=”5001″
$env:MYNAME=”Jane”
node srcindex.js

This process is manageable for our two environment variables but quickly becomes tedious if we must define dozens of environment variables each time the application runs. Defining environment variables in an .env file provides a convenient solution.

Loading .env files in Node.js

Node.js doesn’t natively load .env files, so we must use the dotenv package to load the file and expose the values through process.env.

Start by adding dotenv as a project dependency in the package.json file under the dependencies property:

{
“name”: “node-env-file-demo”,
“version”: “1.0.0”,
“description”: “Node.js example loading .env files”,
“main”: “src/index.js”,
“scripts”: {
“start”: “nodemon src/index.js”
},
“dependencies”: {
“dotenv”: “10.0.0”
},
“devDependencies”: {
“nodemon”: “1.18.4”
},
“keywords”: []
}

Next, download the dependency with the following:

npm install

To load the .env file, we must load the dotenv package and call the configure() function at the start of the index.js file:

require(‘dotenv’).config();
const http = require(‘http’);
const port = parseInt(process.env.PORT, 10) || 5000;
const name = process.env.MYNAME || “Matthew”
http.createServer((request, response) => {
response.writeHead(200, {
‘Content-Type’: ‘text/plain’
});
response.write(‘Hello, ‘ + name + ‘!’);
response.end();
}).listen(port);

Our application is now ready to load .env files.

Defining custom environment variables in Node.js

We can now create the .env file in the project’s root directory.

Customizing an .env file involves defining the environment variable name we want to override at the start of each line, followed by = and the variable’s value. In the example below, we defined new values for the PORT and MYNAME environment variables:

PORT=5001
MYNAME=Jane

Now, run the application with the following command:

node src/index.js

Next, open http://localhost:5001; notice the port changed and the message Hello, Jane! returns.

Preloading dotenv

An alternative to calling require(‘dotenv’).config() in our code is to use the -r or –require command line option to preload dotenv:

node -r dotenv/config src/index.js

This approach injects environment variables into a Node.js application, that otherwise does not support .env files, without editing the original source code.

Using multiple .env files in Node.js

To allow developers to quickly swap between many customized environment files during development, we can configure dotenv to load environment variables from a custom file via the DOTENV_CONFIG_PATH environment variable.

To demonstrate this, create a file called .env.development in the project’s root directory with the following contents:

PORT=5002
MYNAME=Jill

Then, set the DOTENV_CONFIG_PATH environment variable to .env.development and run the application using the preload method we just covered.

On Windows PowerShell, run the application with these commands:

$env:DOTENV_CONFIG_PATH=”.env.development”
node -r dotenv/config src/index.js

On Linux or macOS, run the application with this command:

DOTENV_CONFIG_PATH=.env.development node -r dotenv/config src/index.js

Our application is now available at http://localhost:5002, and will return Hello, Jill!.

Excluding .env files from Git

Because .env files often contain sensitive information, like database connection strings, we don’t want to commit those values to a Git repository. If we did, we would be sharing our passwords with anyone who can access the application source code.

Environment variables neatly avoid this problem because, by their very nature, their values are not defined in a file. However, we must specifically exclude .env files from our Git repository to prevent sharing them.

We can do this by creating a file called .gitignore in the project’s root directory and adding the following line:

.env*

Now, Git ignores the .env file and any other files starting with .env like .env.development, meaning any sensitive information will not end up in our Git repository.

Conclusion

Including environment variables in an .env file is a convenient way to define many related configuration values without managing them as part of your operating system or defining them on the command line each time an application runs.

The post Customizing Node.js <code>.env</code> files appeared first on LogRocket Blog.

Leave a Reply

Your email address will not be published. Required fields are marked *

Send