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!
Oldest comments (19)
Great tutorial. Thx for sharing 👍
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:
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.
Additional tip
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.
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 indevDependencies
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
Further read: dev.to/ismailpe/handling-environme...
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!