NODE_ENV and Environment-Aware Configuration
Objectives
By the end of this lesson, you should be able to:
- Explain what
NODE_ENVis, and what it doesn’t automatically do - Build a configuration module that adapts to
NODE_ENV - Load a different
.envfile per environment
💡 Why this matters:
NODE_ENVis 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
// 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
process.env.NODE_ENV = 'development';
console.log(getConfig());
{ env: 'development', port: 3000, logLevel: 'debug', dbPoolSize: 2 }
process.env.NODE_ENV = 'production';
process.env.PORT = '8080';
console.log(getConfig());
{ 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
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) });
$ 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
- Build
config.js, and confirm it returns different, correct values fordevelopmentandproduction. - Add a
testenvironment case, with its ownlogLevelanddbPoolSize, and confirmNODE_ENV=testpicks it up. - Create
.env.developmentand.env.productionfiles, and confirm the loader picks the right one based onNODE_ENV. - Explain, in one or two sentences, why
getConfig()as a single function is easier to reason about thanprocess.env.NODE_ENV === 'production'checks scattered across many files.
Recap
NODE_ENVis a plain string, it configures nothing by itself, application code decides what to do with it.- A single
getConfig()function, keyed byNODE_ENV, is a better pattern than scattered environment checks throughout a codebase. - Loading
.env.developmentversus.env.productiondemonstrates 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.