Environment Variables and Configuration
Environment variables are a fundamental way to configure your Node.js applications. They allow you to separate configuration from code, making your apps more secure, portable, and easier to deploy across different environments.
What Are Environment Variables?
Environment variables are key-value pairs that exist outside your application code. They're set at the operating system or process level and can be accessed by your program at runtime.
# Setting environment variables in terminal
export PORT=3000
export NODE_ENV=development
export API_KEY=your-secret-key
# Or inline when running a command
PORT=3000 node server.js
Common use cases:
- Secrets: API keys, database passwords, tokens
- Configuration: Port numbers, URLs, feature flags
- Environment detection: Development vs production settings
Accessing Environment Variables with process.env
Node.js provides access to environment variables through the global process.env object:
// Access environment variables
console.log(process.env.PORT); // "3000"
console.log(process.env.NODE_ENV); // "development"
console.log(process.env.HOME); // "/home/user" (system variable)
// Use with defaults
const port = process.env.PORT || 3000;
const debug = process.env.DEBUG === 'true';
Important: Environment Variables Are Strings
All environment variables are strings. You need to convert them to other types:
// PORT is a string "3000", not a number
const port = parseInt(process.env.PORT, 10);
// Boolean conversion
const debug = process.env.DEBUG === 'true';
const enabled = process.env.FEATURE_ENABLED !== 'false';
// Array from comma-separated string
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [];
// JSON parsing
const config = JSON.parse(process.env.CONFIG_JSON || '{}');
The dotenv Package
In development, managing environment variables through the terminal is inconvenient. The dotenv package loads variables from a .env file:
npm install dotenv
Create a .env file in your project root:
# .env
PORT=3000
NODE_ENV=development
DATABASE_URL=postgres://localhost:5432/myapp
API_KEY=sk_test_abc123
SECRET_KEY=super-secret-value
DEBUG=true
Load it in your application:
// Load at the very beginning of your app
require('dotenv').config();
// Now process.env contains values from .env
console.log(process.env.PORT); // "3000"
console.log(process.env.DATABASE_URL); // "postgres://..."
Best Practices for .env Files
1. Never Commit .env to Git
Add .env to your .gitignore:
# .gitignore
.env
.env.local
.env.*.local
2. Create a .env.example Template
Provide a template file that can be committed:
# .env.example - Template for environment variables
# Copy this to .env and fill in your values
PORT=3000
NODE_ENV=development
DATABASE_URL=postgres://localhost:5432/myapp
API_KEY=your-api-key-here
SECRET_KEY=generate-a-secret-key
3. Use Different Files for Different Environments
.env # Default/development
.env.local # Local overrides (not committed)
.env.test # Test environment
.env.production # Production (usually not committed)
Environment-Specific Configuration
Use NODE_ENV to detect the current environment:
const isDevelopment = process.env.NODE_ENV === 'development';
const isProduction = process.env.NODE_ENV === 'production';
const isTest = process.env.NODE_ENV === 'test';
// Configure based on environment
const config = {
port: process.env.PORT || 3000,
logLevel: isProduction ? 'error' : 'debug',
database: {
ssl: isProduction,
poolSize: isProduction ? 20 : 5
}
};
Loading dotenv Correctly
Basic Loading
// app.js or index.js - at the very top!
require('dotenv').config();
// Or with ES modules
import 'dotenv/config';
// Rest of your imports and code
const express = require('express');
Loading from Custom Path
require('dotenv').config({ path: './config/.env' });
// Or multiple files
require('dotenv').config({ path: '.env.local' });
require('dotenv').config(); // .env as fallback
Conditional Loading
// Only load in development
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config();
}
Validating Environment Variables
Always validate required environment variables at startup:
function validateEnv() {
const required = [
'DATABASE_URL',
'API_KEY',
'SECRET_KEY'
];
const missing = required.filter(key => !process.env[key]);
if (missing.length > 0) {
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}
}
// Call at startup
validateEnv();
Security Best Practices
1. Never Log Sensitive Variables
// BAD - might expose secrets in logs
console.log('Config:', process.env);
// GOOD - only log non-sensitive values
console.log('Environment:', process.env.NODE_ENV);
console.log('Port:', process.env.PORT);
2. Use Secrets Management in Production
For production, consider:
- AWS Secrets Manager
- HashiCorp Vault
- Azure Key Vault
- Google Cloud Secret Manager
- Kubernetes Secrets
3. Rotate Secrets Regularly
Design your app to handle secret rotation:
// Periodically refresh secrets
async function refreshSecrets() {
const secrets = await fetchFromSecretManager();
process.env.API_KEY = secrets.apiKey;
}
setInterval(refreshSecrets, 3600000); // Every hour
Common Patterns
Default Values with Nullish Coalescing
// Use || for falsy defaults
const port = process.env.PORT || 3000;
// Use ?? for null/undefined only
const debug = process.env.DEBUG ?? 'false';
Required vs Optional Variables
function getRequiredEnv(key) {
const value = process.env[key];
if (!value) {
throw new Error(`Environment variable ${key} is required`);
}
return value;
}
function getOptionalEnv(key, defaultValue) {
return process.env[key] || defaultValue;
}
// Usage
const apiKey = getRequiredEnv('API_KEY'); // Throws if missing
const port = getOptionalEnv('PORT', 3000); // Uses default if missing
Key Takeaways
- Environment variables are accessed via
process.env - All environment variables are strings - convert as needed
- Use
dotenvpackage to load variables from.envfiles - Never commit
.envfiles with secrets to git - Provide
.env.exampleas a template - Use
NODE_ENVto detect development/production - Always validate required variables at startup
- Don't log sensitive environment variables
Summary
Environment variables are essential for configuring Node.js applications. You've learned how to access them with process.env, use the dotenv package to load from files, handle different environments, and follow security best practices. This foundation is crucial for building production-ready applications that can be deployed across different environments without code changes.
Next, you'll learn about working with JSON data, which is the most common format for configuration files and API data.

