DEV Community

Hien Nguyen Minh
Hien Nguyen Minh

Posted on

Setting NODE_ENV is an Antipattern

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);
}
Enter fullscreen mode Exit fullscreen mode

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";
}
Enter fullscreen mode Exit fullscreen mode

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=production in 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 on NODE_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);
}
Enter fullscreen mode Exit fullscreen mode

✅ Good: Feature-Based

if (process.env.ENABLE_RATE_LIMITING === "true") {
  app.use(rateLimiter);
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This way:

  • NODE_ENV stays reserved for libraries (set it to production in production)
  • APP_ENV is 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)