DEV Community

Maxime
Maxime

Posted on • Originally published at Medium on

Javascript Environment Variables Loading environment variables in JS apps

How to store and consume environment variables for local development

APIs and third-party integrations require developers to use configuration data called environment or config variables. These variables are usually stored in password-protected places like CI tools or deployment pipelines, but how can we use them when we’re developing applications locally?

TL;DR:

  • Don’t store environment variables in source control
  • Use dotenv to read data from your .env file
  • create-react-app forces a namespace on environment variables

This short tutorial will explain one way of loading environment variables into your code when developing locally. The main benefit is that secrets such as API keys are not committed to source control to keep your application safer.

Requirements:

  • A Javascript application
  • A package manager (yarn and npm are great)
  • Node 7+

Set up the variables

Create a file called “.env” in the root of your repository. This file is called a “dot file” and is different from regular files in that it is usually hidden in file browsers.

Most IDEs will allow users to create files without a name, but if that’s not the case, head over to your terminal and cd into your application’s root folder.

touch .env
Enter fullscreen mode Exit fullscreen mode

Next, set up your variables with the format key=value, delimited by line breaks:

API_KEY=abcde
API_URL=https://my-api.com/api
Enter fullscreen mode Exit fullscreen mode

Finally, make sure the .env file is not committed to your repository. This can be achieved by opening (or creating) a .gitignore file and adding this line:

.env # This contains secrets, don't store in source control
Enter fullscreen mode Exit fullscreen mode

Consume the variables

Head to your terminal to install dotenv with your preferred package manager:

# Using npm:
npm i dotenv

# Using yarn:
yarn add dotenv
Enter fullscreen mode Exit fullscreen mode

You’re now ready to read from your .env file. Add this line of code as early as possible in your application. With React apps, that’s usually index.js or App.js, but it’s entirely up to your setup:

require('dotenv').config();
Enter fullscreen mode Exit fullscreen mode

And that’s it! Your application should have access to environment variables via the process.env object. You can double-check by calling:

console.log(process.env);
Enter fullscreen mode Exit fullscreen mode

If all is well, you should see something like:

{
 NODE_ENV: "development",
 API_KEY: "abcde",
 API_URL: "https://my-api.com/api"
}
Enter fullscreen mode Exit fullscreen mode

🎉 You’re now ready to use environment variables in your application!

Now, for those of us that use create-react-app, there’s a catch, and I wish it was documented a little better.

Using create-react-app

Facebook’s create-react-app works a little differently. If you followed the steps above and haven’t ejected the application, all you should see is the NODE_ENV variable. That’s because create-react-app only allows the application to read variables with the REACT_APP_ prefix.

So in order to make our variables work, we’ll need to update our .env file like so:

REACT_APP_API_KEY=abcde
REACT_APP_API_URL=https://my-api.com/api
Enter fullscreen mode Exit fullscreen mode

Once again, verify your setup by logging process.env to the console:

{
 NODE_ENV: "development",
 REACT_APP_API_KEY: "abcde",
 REACT_APP_API_URL: "https://my-api.com/api"
}
Enter fullscreen mode Exit fullscreen mode

And you’re done 😎

Tips

Variables in .env files don’t require quotation marks unless there are spaces in the value.

NO_QUOTES=thisisokay
QUOTES="this contains spaces"
Enter fullscreen mode Exit fullscreen mode

It’s good practice to create a .env.sample file to keep track of the variables the app should expect. Here’s what my own sample file looks like in my current project. Note that it explains where someone might be able to find those keys and URLs.

CONTENTFUL_SPACE_TOKEN="see Contentful dashboard"
CONTENTFUL_API_KEY="see Contentful dashboard"
S3_BUCKET_URL="check AWS"
SHOW_DEBUG_SIDEBAR="if true, show debug sidebar"
Enter fullscreen mode Exit fullscreen mode

Further reading:

Thank you for reading! Do you prefer another way of loading environment variables locally? I’d love to hear about it in the comments below!

Top comments (19)

Collapse
 
asamolion profile image
Muhammad Osama Arshad

Hi there, nice article.

Just have a quick question. Does the dotenv script load the entire .env file into the client side?

If that's the case then wouldn't that expose sensitive data such as DB password etc?

Collapse
 
fabiorosado profile image
Fabio Rosado • Edited

This might be silly but I was wondering exactly the same thing. If you can do console.log(process.env); I wonder if the values are automatically replaced by environment variables perhaps?

--EDIT--
I went ahead and read the link to the 12-factor app and this is exactly what happens. The values are replaced by environment variables with each deploy.

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

Collapse
 
asamolion profile image
Muhammad Osama Arshad

Very cool. Thanks for taking the time to answer.

Collapse
 
deammer profile image
Maxime

Hi Muhammad! The entire .env file is indeed loaded, so all the secrets (including database passwords, in your case) will be exposed on the client, if that's where your app is running. This would obviously be a huge problem in a production environment, but my use case was centered around local development.

Security depends heavily on your deployment pipeline and the kind of system you're building, and I don't want to go too deep on that topic in a comment, but I'll leave you with two things:

  1. If you're developing a client-side app, it should be making calls to an API, not a database. This way, even if the API key is leaked, you can control security by making the API read-only or having a strict CORS policy.
  2. You could use the code below to make sure your client-side app doesn't expose secrets:
if (process.env.NODE_ENV !== 'production') {
  require('dotenv').config();
}

Hope this answers your question!

Collapse
 
asamolion profile image
Muhammad Osama Arshad

I see. I was thinking of using this in production in my current client's app. Thanks for pointing this out.

Dodged a bullet there.

Collapse
 
rannn505 profile image
Ran Cohen

Hi Everyone,

Firstly, I'd like to commend @deammer for this insightful article.

Reading through the comments, I noticed some concerns regarding handling of secrets and configuration deployments. As your projects scale, complexities with managing more code, services, and environment variables are bound to increase. That's where a third-party tool can come in handy.

I recommend considering a tool like Configu for managing your app settings. It's an open-source, language-agnostic solution that streamlines configuration management. By using Configu, you can alleviate the burden of dealing with raw-text based files and orchestrating configuration data across various formats during build, deployment, or runtime.

For more insights, feel free to check out my article that dives deeper into this topic: Configu - Unleashing the Power of Configuration as Code

Happy coding!

Collapse
 
qm3ster profile image
Mihail Malo

So, the most interesting part is missing:
How do I actually shove some .env files into the deployment environment?
Are we talking about functions? Container images? VM images? Persistent VMs?

Collapse
 
deammer profile image
Maxime

Hi Mihail! This article is focused on local development, in part because there are countless ways to execute deployments.

For instance, tools like Travis, Heroku, and Netlify provide a UI that lets you set up environment variables. If you're using a VM-like environment like EC2 or Digital Ocean, you can actually upload a .env file directly. If you're using a container system like Docker, you can use Compose or config arguments to set environment vars.

Hope this helps!

Collapse
 
qm3ster profile image
Mihail Malo

So, using the dotenv module is essentially the local version of those managed environments' ENV configs?
Then, perhaps, the best way to use it is node -r dotenv/config your_script.js, and only include it in devDependencies so it's not present at all in production?
...but in that case it's not very different from just putting a cross-env at the start of your development script.
I guess I still don't entirely understand what unique niche dotenv is the best fit for.

Thread Thread
 
misterhtmlcss profile image
Roger K.

Hi Mihail, have you written anything about how you go about protecting while using your secret keys? I'm interested to learn more and if you have then please post a link. These kinds of nuts and bolts articles are in such dire need from my point of view.

Thread Thread
 
qm3ster profile image
Mihail Malo

Well, so far as protecting secrets, at the moment I believe that these are indeed best set as environmental variables of the deployment environment.
I know some people use git hooks that test that they aren't committing any secrets, but I believe these are brittle and only give a false sense of security.
A rule that seems to work for me is - if you want to make sure something is never committed, don't put it in the project directory. Don't test with it.

But then there's still the app's responsibility of not sending the secrets to any users.
Corollary: Don't rely on this as a way to protect the secrets from malicious developers or even accidental disclosure. If they can get code into production, they can compromise any data available in the production environment. Even if all deployment goes through CI from a protected branch, all you get is blame a long time later.
Hence, all secrets must have the minimum permissions possible. For example, every service should have its own database login/connection string. Not for permissions alone, but so that it can be easily replaced when compromised.
Another example (although not usually provided through ENV since they are reissued at runtime) could be asymmetric JWT algorithms, where most services can only verify the token but not issue it.

Thread Thread
 
misterhtmlcss profile image
Roger K.

Thank you! I can't read input like this often enough. It really helps me. I wish experienced devs talked more about it, since it's so key to delivering a basic professional experience

Collapse
 
kyleparisi profile image
Kyle Parisi

Additional tip

dotenv.config({ path: ".env" });
// Set any missing with defaults
dotenv.config({ path: ".env.example" });
Collapse
 
trystonperry profile image
Tryston Perry

Thank you! I've been trying to figure this out for a while and couldn't find a resource on how to do it.

Collapse
 
deammer profile image
Maxime

Glad you found this useful!

Collapse
 
rmoskal profile image
Robert Moskal

I don't think there's any harm in keeping development secrets under version control in many, many cases. Especially when you are using something like compose. It speeds up the onboarding of new developers. Production secrets are another matter.

Collapse
 
obstschale profile image
Hans-Helge Buerger

Great tutorial. Thx for sharing 👍

Collapse
 
sd031 profile image
Sandip Das

good read before going to sleep :)

Collapse
 
ismailpe profile image
Ismail PE