Article summary
Let’s talk about using dotenv for node property management and why you might want to use dotenv-defaults instead.
I get it. You love simplicity, and it doesn’t get any simpler than dotenv. You create a fresh React app, add in the dotenv dependency, and start adding properties to a .env
file. That’s all there is to it; you’re done! Err wait, you don’t want to have to email every developer the properties for this app. No problem, you just create a .env.example
file and toss all the properties in there (except for secrets of course). So you commit that example file to your repository, and now devs know what properties to start with in their .env
file when they clone your repo. It’s a little more complicated, but still pretty simple.
The Problem
Aw, crap! Your team members complain that their environments keep breaking every time they pull if there’s a property change since their local .env
doesn’t get updated. No problem. You just add a new team norm that, every time you make a property change, you send a message to everyone so they know they have to update their .env
files. That’s not the most simple, but it’s not too bad.
Oh, shoot! You missed an update to production and had to scramble to fix it on release night. There were so many updated properties you missed one! Okay, you can still handle that. You create a release document and create another team norm to add property changes to it as part of development work. Sigh. This is…not the simplest. Why did it get so messy?
The Core Issue
The core issue for all the complications with this setup is the disconnect between a local edited file and a repo-committed file. You should document those example properties somewhere, so a file in the repository is as good as any place. But, nothing is actually using those; they are purely documentation. The .env
file itself isn’t designed to be committed, as it will be different on every environment. You could create a symlink for .env
to .env.example
and put it in your README as a part of the setup, and that can work. The problem is that forces you into using dev environment values for the example file, and it’s annoying to customize.
Dev environment example values aren’t the end of the world as there are valid pros and cons to choosing which environment your example values are for. It’s nice to have the option to choose production if you want, though, as it can result in much smoother releases. Difficulty in customizing the value, however, is pretty frustrating. If I want to run my app with any non-standard value, I would need to either add it to the example file and remember not to commit it or delete my symlink and add a real .env
file. If this happens often enough, we’re back to this not being simple anymore. Depending on your environment and testing setup, this could happen quite often!
The Solution
Allow me to introduce you to your new best friend: dotenv-defaults! This module works almost identically to dotenv but with a few helpful twists. When it loads properties, it first looks for a .env.defaults
file and parses the properties. It then looks for a .env
file and parses the properties from there, overriding any properties from the defaults. It then takes the merged properties and puts them into process.env as normal dotenv does.
This means instead of an example file, you actually have a true defaults file that you can commit into your repository. You could make this represent local environment defaults so that devs only need to put secrets in a .env
file. Alternatively, you could make the defaults production-like, so that non-secret property changes are handled for free during deployments.
The result is a much smoother development experience. You still have some issues as changing secrets is still a pain, plus if you choose prod defaults you’ll still need to distribute new properties that differ, like connection strings. Overall though, this will happen much less and doesn’t cost you anything! That’s because dotenv-defaults is a drop-in replacement for dotenv. Just update your imports to use the new module, and nothing else has to change. That might be a lot of files to change, but it’s so simple you can also just do a find and replace.
Keep in mind this is still a relatively simple solution. If you have complicated property needs for large applications, neither of these modules will suffice. You’ll have to use something more complex and feature-rich, like node-config. That said, if dotenv was working great for you before, there’s no reason you shouldn’t switch to dotenv-defaults. Save your developers some hassle and even make your deployments easier!