loading...

Javascript Environment Variables Loading environment variables in JS apps

deammer profile image Maxime Originally published at Medium on ・3 min read

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

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

API_KEY=abcde
API_URL=https://my-api.com/api

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

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

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();

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);

If all is well, you should see something like:

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

🎉 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

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"
}

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"

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"

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!

Posted on by:

deammer profile

Maxime

@deammer

Gradient developer and JAMstack advocate 🥑 http://pronoun.is/he

Discussion

markdown guide
 

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?

 

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

 

Very cool. Thanks for taking the time to answer.

 

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!

 

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.

 

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?

 

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!

 

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.

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.

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.

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

 

Additional tip

dotenv.config({ path: ".env" });
// Set any missing with defaults
dotenv.config({ path: ".env.example" });
 

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

 

Glad you found this useful!

 

good read before going to sleep :)

 

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.

 

Great tutorial. Thx for sharing 👍