Environment variables are an important part of today's web applications. According to Heroku's influential Twelve Factor philosophy, env vars are the preferred tool for managing configuration that changes across deployments.
Useful as they may be, without discipline env vars can turn into a liability - acting as chunks of mutable global state sprinkled throughout your program. Here we'll look at three env var usage antipatterns that are commonly seen in web apps.
1) Lack of Validation
Environment variables are a type of input to your program, and as such should be treated with suspicion and care. While malicious inputs are generally unlikely in this case, if you expect a number or an email address and that expectation is not met, it's best to throw an error as soon as possible, ideally at deploy time. This makes the runtime behavior of your app much more predictable and eliminates some of the guesswork of configuring your app for others.
2) Multiple Points of Access
While validating inputs from the environment is an essential first step, as always it's important to keep things DRY. This means your logic for accessing and validating an env var should be contained in a single place, and not duplicated wherever the variable is accessed. Ideally all direct contact with, and validation of, env vars should happen in a single place in your program. This also makes it easier to see at a glance what your program's configuration dependencies are.
3) Mutation
From the program's perspective, environment variables are a form of global state. Global state is not inherently bad if it remains static, but once it changes it becomes a rich breeding ground for bugs and unexpected behavior. By wrapping env var access in a read-only data structure, you can side-step these pitfalls and increase the predictability of your code.
A Helpful Approach
The antipatterns above are applicable to any language or framework. A pattern I've found useful for sidestepping these issues is to centralize all environment accesses through a single module that does nothing else. When written in a declarative way, this module also serves as a form of executable documentation for anyone deploying the app. The rest of your code can import this module and use it as an immutable map of verified environment attributes.
For nodejs programs, I maintain and use envalid, which provides an implementation of this approach, and should effectively mitigate the env vars footguns described above.