If you've written Node.js code, you've probably done this:
if (process.env.NODE_ENV === "production") {
app.use(rateLimiter);
}
if (process.env.NODE_ENV === "development") {
console.log("Debug:", data);
}
This is an antipattern. According to the Node.js documentation, setting NODE_ENV to anything but production is considered an antipattern.
The Problem
When you branch on NODE_ENV, you're coupling your application's behavior to the environment name, not the actual configuration it needs. This creates a fundamental testing problem: your tests can't verify what actually runs in production.
The Testing Trap
Consider this common pattern:
if (process.env.NODE_ENV === "production") {
app.use(rateLimiter);
app.use(helmet());
logger.level = "error";
} else {
logger.level = "debug";
}
Your tests run with NODE_ENV=test, so they never execute the production code path. Your tests pass, but you have no idea if your rate limiter configuration is correct, if helmet breaks your API responses, or if your error logging actually works. You're testing a different application than the one your users run.
The Staging Nightmare
The problem gets worse with staging environments. You want staging to mirror production for realistic testing, but you also need it to behave differently (different database, different external APIs, etc.). With NODE_ENV, you're forced to choose:
- Set
NODE_ENV=productionin staging? Now your staging environment might enable production-only features (like payment processing) that you don't want. - Set
NODE_ENV=staging? Your code branches onNODE_ENV === "production", so staging behaves like development, not production.
You can't have staging behave like production for testing while also being different for deployment. NODE_ENV forces you into this impossible choice.
The Solution
Instead of checking the environment, check the actual feature or configuration you need:
❌ Bad: Environment-Based
if (process.env.NODE_ENV === "production") {
app.use(rateLimiter);
}
✅ Good: Feature-Based
if (process.env.ENABLE_RATE_LIMITING === "true") {
app.use(rateLimiter);
}
What About Libraries?
Some libraries, like ReactJS and ExpressJS use NODE_ENV to turn on development-only features such as extra logs or warnings. That’s fine, and you can rely on it during development.
But keep this in mind:
Setting NODE_ENV !== 'production' should only be for enabling those library-level debug features not for controlling your own app logic. Use it intentionally, know what libraries will do with it, and avoid treating it as a catch-all switch for your behavior.
If You Need to Know the Environment
Sometimes you legitimately need to know which environment you're running in (for logging, monitoring, or display purposes). In that case, use a different variable name like APP_ENV:
// ✅ Good: Use APP_ENV for informational purposes
const environment = process.env.APP_ENV || "development";
console.log(`Running in ${environment} environment`);
// ❌ Bad: Don't use NODE_ENV for this
const environment = process.env.NODE_ENV; // Reserved for libraries
This way:
-
NODE_ENVstays reserved for libraries (set it toproductionin production) -
APP_ENVis your application's way to identify the environment - You can still use feature flags for behavior control
Key Takeaway
Stop using NODE_ENV to control your application's behavior. Use explicit feature flags and configuration instead. Your tests will actually test what runs in production, and you'll avoid the "works in development, breaks in production" trap.
The next time you're about to write if (process.env.NODE_ENV === "production"), ask yourself: "What am I actually trying to control?" Then use a specific configuration variable instead.
Top comments (0)