CodingNic

From Development to Production

NODE_ENV and Environment-Aware Configuration

From Development to Production 15 min read

NODE_ENV and Environment-Aware Configuration

Objectives

By the end of this lesson, you should be able to:

  • Explain what NODE_ENV is, and what it doesn’t automatically do
  • Build a configuration module that adapts to NODE_ENV
  • Load a different .env file per environment

💡 Why this matters: NODE_ENV is the single most common convention for telling a Node.js application which environment it’s running in, but it doesn’t configure anything by itself, this lesson builds the actual mechanism on top of it.

⚠️ A note on verification: every snippet and every output in this lesson was actually run.

What NODE_ENV Actually Does

NODE_ENV is just an environment variable, read as process.env.NODE_ENV, a plain string, nothing more. Some libraries check it themselves, Express enables extra caching for view rendering when it’s 'production', but for anything specific to an application’s own code, NODE_ENV only does what that code explicitly checks it for.

A Configuration Module

javascript
// config.js
function getConfig() {
  const env = process.env.NODE_ENV || 'development';

  const base = {
    env,
    port: Number(process.env.PORT) || 3000,
  };

  const perEnv = {
    development: { logLevel: 'debug', dbPoolSize: 2 },
    test: { logLevel: 'silent', dbPoolSize: 1 },
    production: { logLevel: 'info', dbPoolSize: 20 }
  };

  return { ...base, ...perEnv[env] };
}

module.exports = { getConfig };

One function, one place the rest of the application reads configuration from, getConfig() merges environment-specific defaults (perEnv[env]) with values that can still be overridden per deployment (PORT), rather than scattering process.env.NODE_ENV === 'production' checks across the codebase.

Reading It Per Environment

javascript
process.env.NODE_ENV = 'development';
console.log(getConfig());
text
{ env: 'development', port: 3000, logLevel: 'debug', dbPoolSize: 2 }
javascript
process.env.NODE_ENV = 'production';
process.env.PORT = '8080';
console.log(getConfig());
text
{ env: 'production', port: 8080, logLevel: 'info', dbPoolSize: 20 }

Same function, same code, different, correct output, dbPoolSize: 20 in production versus 2 in development reflects a real, deliberate choice, a production database connection pool needs to handle real concurrent traffic, a local development database doesn’t.

Loading a Different .env File Per Environment

javascript
const dotenv = require('dotenv');
const path = require('path');

const env = process.env.NODE_ENV || 'development';
const envFile = `.env.${env}`;
dotenv.config({ path: path.resolve(__dirname, envFile) });
text
$ NODE_ENV=development node loadenv.js
Loaded .env.development
JWT_SECRET present: true
DATABASE_URL: postgresql://localhost:5432/myapp_dev

$ NODE_ENV=production node loadenv.js
Loaded .env.production
JWT_SECRET present: false
DATABASE_URL:

.env.development and .env.production are separate files, each with its own values, only the one matching the current NODE_ENV is loaded. The production file above was deliberately left with empty values, in a real deployment, production secrets come from the hosting platform’s own environment variable configuration, not a .env.production file, which would otherwise need to exist on disk, and risk being committed, exactly the problem the next lesson addresses directly.

Try It

  1. Build config.js, and confirm it returns different, correct values for development and production.
  2. Add a test environment case, with its own logLevel and dbPoolSize, and confirm NODE_ENV=test picks it up.
  3. Create .env.development and .env.production files, and confirm the loader picks the right one based on NODE_ENV.
  4. Explain, in one or two sentences, why getConfig() as a single function is easier to reason about than process.env.NODE_ENV === 'production' checks scattered across many files.

Recap

  • NODE_ENV is a plain string, it configures nothing by itself, application code decides what to do with it.
  • A single getConfig() function, keyed by NODE_ENV, is a better pattern than scattered environment checks throughout a codebase.
  • Loading .env.development versus .env.production demonstrates the mechanism, real production secrets come from the hosting platform directly, not a file on disk, covered next.

Next lesson: managing secrets correctly across every environment, not just keeping them out of one .env file.