Difficulty: Beginner
Environment variables are the standard way to configure Node.js applications across different environments (development, testing, staging, production). They allow you to change application behavior without modifying code - database URLs, API keys, port numbers, and feature flags can all be set externally. This separation of configuration from code is one of the twelve-factor app principles and is essential for secure, deployable applications.
The `dotenv` package loads environment variables from a `.env` file into `process.env`. You create a `.env` file in your project root with KEY=VALUE pairs, call `require('dotenv').config()` at the top of your entry point, and access values with `process.env.KEY`. The .env file should never be committed to version control - add it to .gitignore and provide a `.env.example` file with placeholder values so other developers know which variables to set.
Configuration management goes beyond just reading environment variables. A well-structured config module validates that all required variables are present at startup (fail fast instead of crashing later), provides defaults for optional values, and groups related settings into logical objects. This prevents the common bug where a missing environment variable causes a cryptic error deep in your application long after startup.
The `NODE_ENV` variable is a convention used by Express and many npm packages. Setting it to 'production' enables optimizations in Express (view caching, less verbose errors), and many libraries use it to adjust their behavior. Common values are 'development', 'test', 'staging', and 'production'. Always set NODE_ENV explicitly in your deployment - Express defaults to 'development' if it is not set, which has performance implications.
Secrets management in production requires more than just .env files. Production secrets (database passwords, API keys, JWT secrets) should be stored in environment variables provided by your hosting platform (Heroku Config Vars, AWS Parameter Store, Docker secrets, Kubernetes ConfigMaps). Never store production secrets in files that could be accidentally committed or accessed by unauthorized users. Use strong, unique values - a JWT secret should be at least 32 random characters.
// Load .env file first
require('dotenv').config();
// config.js - centralized configuration
const config = {
port: parseInt(process.env.PORT, 10) || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
db: {
url: process.env.DATABASE_URL,
pool: {
min: parseInt(process.env.DB_POOL_MIN, 10) || 2,
max: parseInt(process.env.DB_POOL_MAX, 10) || 10
}
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '1h'
},
cors: {
origin: process.env.CORS_ORIGIN || 'http://localhost:5173'
},
isProduction: process.env.NODE_ENV === 'production',
isDevelopment: process.env.NODE_ENV !== 'production'
};
console.log('Config loaded:');
console.log(' Port:', config.port);
console.log(' Environment:', config.nodeEnv);
console.log(' DB Pool:', config.db.pool);
console.log(' JWT Expiry:', config.jwt.expiresIn);
console.log(' Is Production:', config.isProduction);
The config module centralizes all environment variable access in one place. Values are parsed to the correct type (parseInt for numbers), defaults are provided for optional values, and derived booleans (isProduction) simplify conditional logic throughout the app.
require('dotenv').config();
function validateEnv() {
const required = [
'DATABASE_URL',
'JWT_SECRET'
];
const missing = required.filter(key => !process.env[key]);
if (missing.length > 0) {
console.error('Missing required environment variables:');
missing.forEach(key => console.error(` - ${key}`));
console.error('\nCheck your .env file or environment configuration.');
process.exit(1);
}
// Validate specific values
if (process.env.JWT_SECRET && process.env.JWT_SECRET.length < 32) {
console.warn('WARNING: JWT_SECRET should be at least 32 characters for security');
}
const port = parseInt(process.env.PORT, 10);
if (process.env.PORT && (isNaN(port) || port < 1 || port > 65535)) {
console.error('PORT must be a number between 1 and 65535');
process.exit(1);
}
console.log('Environment validation passed');
}
// Call at startup before anything else
// validateEnv();
// Demo with mock environment
process.env.DATABASE_URL = 'postgresql://localhost/myapp';
process.env.JWT_SECRET = 'a-very-long-secret-key-for-production-use-only-32chars';
validateEnv();
Validation runs at startup and fails fast if required variables are missing. This prevents the application from starting in a broken state. It also checks value constraints like minimum JWT secret length and valid port numbers.
// Example .env file structure:
// # Server
// PORT=3000
// NODE_ENV=development
//
// # Database
// DATABASE_URL=postgresql://postgres:password@localhost:5432/myapp
//
// # Authentication
// JWT_SECRET=your-secret-key-at-least-32-characters
// GOOGLE_CLIENT_ID=xxxx.apps.googleusercontent.com
//
// # External Services
// AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
// AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
// AWS_REGION=us-east-1
// AWS_BUCKET_NAME=my-app-uploads
// .env.example (committed to git, no real values):
const envExample = `# Server
PORT=3000
NODE_ENV=development
# Database
DATABASE_URL=postgresql://postgres:password@localhost:5432/myapp
# Auth (generate a strong secret!)
JWT_SECRET=change-me-to-a-random-32-char-string
# AWS (optional, falls back to local storage)
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=us-east-1
AWS_BUCKET_NAME=`;
console.log('.env.example contents:');
console.log(envExample.split('\n').length, 'lines');
console.log('\nRules:');
console.log('1. Never commit .env to git');
console.log('2. Commit .env.example with placeholders');
console.log('3. Use strong, unique secrets in production');
The .env file contains actual secrets and is git-ignored. The .env.example file documents all required variables with placeholder values and is committed so new developers know what to configure. Comments explain the purpose of each variable.
.env files, dotenv, process.env, config management, secrets, NODE_ENV