Decoupling configuration from the application
What Are Environment Variables?
Two fundamental components of any computer programming language are variables and constants. Like independent variables in a mathematical equation, these take on values that change the results of the program. Variables and constants both represent unique memory locations containing data the program uses in its calculations. The difference between the two is that variables values may change during execution, while constant values are unchanging.
An environment variable is a variable whose value is set outside the program, typically through functionality built into the operating system or microservice. An environment variable is made up of a name/value pair, and any number may be created and available for reference at a point in time.
# Meteorite dataset from Nasa Open Data Portal
REACT\_APP\_METEORITE\_LANDING\_HOMEPAGE="https://data.nasa.gov/Space-Science/Meteorite-Landings/gh4g-9sfh"
REACT\_APP\_METEORITE\_STRIKE\_DATASET="https://data.nasa.gov/resource/y77d-th95.json"
During application initialization, these are loaded into process.env
and accessed by suffixing the name of the environment variable as shown below.
fetch(process.env.REACT\_APP\_METEORITE\_STRIKE\_DATASET)
.then((response) =\> {
return response.json();
})
.then((strikesJSON) =\> {
this.setState({ meteoriteStrikes: strikesJSON });
this.setState({ isDataLoaded: true});
});
At runtime, the reference to the environment variable name is replaced with its current value. In this case, process.env.REACT\_APP\_METEORITE\_STRIKE\_DATASET
is replaced by its value, https://data.nasa.gov/resource/y77d-th95.json
.
The primary use case for environment variables is to limit the need to modify and re-release an application due to changes in configuration data. From the example above, when REACT\_APP\_METEORITE\_STRIKE\_DATASET
's URL changes there’s no need for source code alterations, testing, and deployment of the modified application.
Modifying and releasing application code is relatively complicated and increases the risk of introducing undesirable side effects into production. When the URL is defined by an environment variable instead of the application, the change process consists of checking the validity of the new URL, updating the corresponding environment variable using an operating system command or updating a configuration file, and testing affected application function(s) to ensure the application still works as expected.
Use cases for environment variables include but are not limited to data such as:
- Execution mode (e.g., production, development, staging, etc.)
- Domain names
- API URL/URI’s
- Public and private authentication keys (only secure in server applications)
- Group mail addresses, such as those for marketing, support, sales, etc.
- Service account names
What these have in common are their data values change infrequently and the application logic treats them like constants, rather than mutable variables.
Next, let’s look at how to utilize environment variables using the native operating system, the NPM package dotenv, and webpack.
Environment Variables in NodeJS
Using environment variables in backend applications relies on operating system commands to define the environment variable and its value. A system administrator may define these using a command line interface, but it typically makes more sense to do so via a shell script. Environment variables typically aren’t globally accessible across the OS, they usually session-specific. For example, using the Linux command line:
setenv REACT\_APP\_METEORITE\_LANDING\_HOMEPAGE = "https://data.nasa.gov/Space-Science/Meteorite-Landings/gh4g-9sfh"
At runtime, NodeJS automatically loads environment variables into process.env
to make them available to the application. For example, fetch(process.env.REACT\_APP\_METEORITE\_STRIKE\_DATASET)
.
Management and manipulation of environment variables differ from operating system to operating system. Also, this varies across different microservices environments, like Heroku, where managing environment variables are performed using an administration panel. Due to this, understanding platform-specific factors is essential before using environment variables in your application.
One way to minimize these differences is to use the cross-env NPM package which provides an operating system independent POSIX-compatible command to set environment variables..
Environment Variables in the dotenv Package
Support for using environment variables in frontend applications isn’t an “out-of-the-box” feature of either the browser or Javascript; a package like dotenv is required to enable it. For the record, both frontend and backend applications may utilize dotenv.
Using this package is as easy as,
**import** dotenv **from**'dotenv';
dotenv.config();
console.log(process.env.REACT\_APP\_METEORITE\_STRIKE\_DATASET);
This technique externalizes data by moving it from source code into environment variables in a .env
file. Adding the .env
file name to .gitignore prevents git push commands from uploading it to the GitHub repo where, for public repos, it would be available to anyone.
Environment variables in .env
are formatted as name=value, lines starting with # are treated as comments, and blank lines are ignored. For example,
# Meteorite dataset from Nasa Open Data Portal
REACT\_APP\_METEORITE\_LANDING\_HOMEPAGE="https://data.nasa.gov/Space-Science/Meteorite-Landings/gh4g-9sfh"
REACT\_APP\_METEORITE\_STRIKE\_DATASET="https://data.nasa.gov/resource/y77d-th95.json"
However, many popular packages such as Create React App (react-scripts), Gatsby, GraphQL CLI, Node Lambda, and more already include dotenv. If you already use one of these packages dotenv may already be available for use in your application. For example, the code snippets above are from an application generated by Create React App, which requires environment variables to be prefixed by REACT\_APP\_
.
In the case of Create React App, there is no need to call dotenv.config()
since node\_modules/react-scripts/config/env.js
automatically populates process.env
with the contents of the .env
file when the application starts. For an example of a Create React App refer to the Meteorite Explorer repo on GitHub.
Since the browser environment isn’t secure applications must take special care not to expose sensitive information, like application secrets. For additional information about how to protect frontend environments check out “Protect Application Assets: How to Secure Your Secrets”.
Environment Variables in webpack
webpack is a bundler that transforms, bundles or packages many different modules, resources, and assets in an application together for use in a browser. One common use of webpack is to prepare an application for production deployment. For example, Create React App’s build script uses webpack to create the build directory containing the production version of an application.
Although webpack implements support for using environment variables it’s as an option of the webpack command. For example,
webpack --env.NODE\_ENV=local
Multiple environment variables are supported by specifying more than one --env option in the webpack command. These are referenced in webpack configuration files (e.g., webpack.config.js) as env. suffixed by the environment variable name. For example, console.log(env.NODE\_ENV)
.
webpack configuration files may also reference environment variables defined by the operating system using process.env
just like any other Javascript module. Consider this example from webpack.config.prod.js in Create React App.
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE\_SOURCEMAP !== 'false';
Wrapping It Up
“Abstraction brings the world into more complex, variable relations; it can extract beauty, alternative topographies, ugliness, and intense actualities from seeming nothingness.” — Jerry Saltz
Using environment variables is one technique to make your app easier to configure by separating infrequently changing data from your code. But as simple as this technique may be, its use is influenced by considerations such as the application type (frontend or backend) and the operating environment (operating system or microservice).
Exploiting environment variables is easy, but understanding their nuances and being able to efficiently and securely utilize them is one factor that sets experienced Web Developers apart from inexperienced developers. As with any technology, the trick isn’t knowing how to use something, it’s knowing when to use it.
Top comments (10)
There is a much better way to set environment variables in NodeJS. I've written a detailed post - How I Setup Environment Variables in NodeJS. No, it’s not “dotenv”
Thanks for this Gijo. I’ll read your post and will research. I typically place my .env’s in an encrypted password vault, but sharing changes with team members an making them available to new team members can be cumbersome. Thanks for showing an alternative method.
Thanks. Check it out and give me your feedback
I've taken a look at your article and your approach has merit. I'm still gathering my thoughts on this, but as of this moment I still prefer an approach (independent from any particular
.env
package) of documenting the names of the environment variables in the projectreadme.md
, but still excluding the.env
file from the project.However, something I need to explore is setting up
.env
files unique to each environment. Indotenv
this can be done by customizing the path in the call to theconfig
function. For example.env.production
,.env.staging
,.env.development
, etc.Having said this though, there may be an opportunity to simplify this by creating a wrapper package to do this outside of application code.
This is a good topic for discussion and I'm interested in any additional comments and suggestions you might have.
For me, that one works prefectly fine. Like I can commit my
.env
files, colleagues can override it using.env.local
, set environment specific variables like.env.staging
.Initially, as you said I updated everything in the readme and added
.env
to.gitignore
. However, I tell everyone that there is a new variable in the readme. Most of the time front-end devs come to us and says "this thing doesn't start!". Me "pls update env file, run migration and try again. If not I'll come"But if you add
.env
from.gitignore
and add everything in readme, then what's the point of it? Someone who got access to your git repo just need that readme right?I don't add the environment variable values to the
readme
. Only the names, a description, and a sample value (not the real value).I understand the downsides, and I'm revisiting my use of environment variables because there are downsides as you've pointed out. Using an encrypted vault for secrets like I'm currently doing still means new devs need help setting things up.
ok got it. Could pls explain bit more about "vault of secrets", how does it work? where do you store it?
I use 1Password, which is a commercially available password keeper, to store information, not just about my personal accounts but also to keep information about the projects I participate in.
There are quite a few different products that do this. The important thing is to pick one that's encrypted, easy to use, and works well on your OS.
Some teams I've worked on use a vault like this with shared credentials.
Nice! Thanks for the info
Anytime! This has been a great discussion and its making me rethink what I'd previously taken for granted. Thank you!