Environment Variables
Environment variables allow you to configure your application differently for development, testing, and production without changing code. They’re essential for keeping secrets safe.
What are Environment Variables?
Variables set outside your code that your application can access:
// Access environment variables
console.log(process.env.NODE_ENV); // "development" or "production"
console.log(process.env.PORT); // "3000"
console.log(process.env.DATABASE_URL);
Common uses:
- API keys and secrets
- Database connection strings
- Port numbers
- Environment mode (dev/prod)
- Feature flags
Using process.env
Built into Node.js:
const express = require('express');
const app = express();
// Use environment variable with fallback
const PORT = process.env.PORT || 3000;
const NODE_ENV = process.env.NODE_ENV || 'development';
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
console.log(`Environment: ${NODE_ENV}`);
});
Setting Environment Variables
Command Line (Temporary)
Linux/Mac:
PORT=4000 node server.js
NODE_ENV=production PORT=8080 node server.js
Windows (CMD):
set PORT=4000
node server.js
Windows (PowerShell):
$env:PORT=4000
node server.js
package.json Scripts
{
"scripts": {
"start": "node server.js",
"dev": "NODE_ENV=development nodemon server.js",
"prod": "NODE_ENV=production node server.js"
}
}
Cross-platform with cross-env:
npm install cross-env --save-dev
{
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon server.js",
"prod": "cross-env NODE_ENV=production node server.js"
}
}
Using .env Files
Install dotenv
npm install dotenv
Create .env File
Create .env in your project root:
# .env
PORT=3000
NODE_ENV=development
DATABASE_URL=mongodb://localhost:27017/myapp
API_KEY=your_secret_api_key_here
JWT_SECRET=super_secret_key
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
Important: Add .env to .gitignore!
# .gitignore
.env
node_modules/
Load .env in Your App
// Load .env file (at the very top of your main file)
require('dotenv').config();
const express = require('express');
const app = express();
// Now use environment variables
const PORT = process.env.PORT || 3000;
const DATABASE_URL = process.env.DATABASE_URL;
const API_KEY = process.env.API_KEY;
console.log('Database:', DATABASE_URL);
console.log('API Key:', API_KEY ? '***' : 'Not set');
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
ES Modules
// server.js (ES modules)
import 'dotenv/config';
import express from 'express';
const app = express();
const PORT = process.env.PORT || 3000;
app.listen(PORT);
Different Environments
Multiple .env Files
Common pattern:
.env # Local development (gitignored)
.env.example # Template (committed to git)
.env.test # Testing environment
.env.production # Production (never commit!)
.env.example (commit this):
PORT=3000
NODE_ENV=development
DATABASE_URL=mongodb://localhost:27017/myapp
API_KEY=your_api_key_here
JWT_SECRET=your_jwt_secret
Loading Specific .env Files
// Load different file based on NODE_ENV
require('dotenv').config({
path: `.env.${process.env.NODE_ENV}`
});
// Or specify path directly
require('dotenv').config({ path: '.env.production' });
Environment-Specific Configuration
const config = {
development: {
port: 3000,
database: 'mongodb://localhost:27017/dev',
logLevel: 'debug'
},
production: {
port: process.env.PORT,
database: process.env.DATABASE_URL,
logLevel: 'error'
},
test: {
port: 3001,
database: 'mongodb://localhost:27017/test',
logLevel: 'silent'
}
};
const env = process.env.NODE_ENV || 'development';
const currentConfig = config[env];
module.exports = currentConfig;
Practical Examples
Database Connection
require('dotenv').config();
const DATABASE_URL = process.env.DATABASE_URL;
if (!DATABASE_URL) {
console.error('DATABASE_URL not set!');
process.exit(1);
}
// Use in connection
connectToDatabase(DATABASE_URL);
API Keys
require('dotenv').config();
const express = require('express');
const app = express();
const API_KEY = process.env.API_KEY;
// Middleware to check API key
function requireApiKey(req, res, next) {
const key = req.headers['x-api-key'];
if (key === API_KEY) {
next();
} else {
res.status(401).json({ error: 'Invalid API key' });
}
}
app.get('/api/data', requireApiKey, (req, res) => {
res.json({ data: 'Secret data' });
});
Email Configuration
require('dotenv').config();
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASSWORD
}
});
async function sendEmail(to, subject, text) {
await transporter.sendMail({
from: process.env.EMAIL_FROM,
to,
subject,
text
});
}
Feature Flags
require('dotenv').config();
const ENABLE_ANALYTICS = process.env.ENABLE_ANALYTICS === 'true';
const ENABLE_PAYMENTS = process.env.ENABLE_PAYMENTS === 'true';
app.get('/api/config', (req, res) => {
res.json({
features: {
analytics: ENABLE_ANALYTICS,
payments: ENABLE_PAYMENTS
}
});
});
// Conditional middleware
if (ENABLE_ANALYTICS) {
app.use(analyticsMiddleware);
}
Configuration Module Pattern
Create a dedicated config file:
config.js:
require('dotenv').config();
function required(name) {
const value = process.env[name];
if (!value) {
throw new Error(`Environment variable ${name} is required`);
}
return value;
}
function optional(name, defaultValue) {
return process.env[name] || defaultValue;
}
module.exports = {
// Server
port: optional('PORT', 3000),
nodeEnv: optional('NODE_ENV', 'development'),
// Database
databaseUrl: required('DATABASE_URL'),
// Security
jwtSecret: required('JWT_SECRET'),
apiKey: required('API_KEY'),
// Email
smtp: {
host: required('SMTP_HOST'),
port: required('SMTP_PORT'),
user: required('SMTP_USER'),
password: required('SMTP_PASSWORD')
},
// Features
enableAnalytics: optional('ENABLE_ANALYTICS', 'false') === 'true',
// Helper
isDevelopment: function() {
return this.nodeEnv === 'development';
},
isProduction: function() {
return this.nodeEnv === 'production';
}
};
server.js:
const config = require('./config');
const express = require('express');
const app = express();
console.log('Environment:', config.nodeEnv);
console.log('Analytics enabled:', config.enableAnalytics);
app.listen(config.port, () => {
console.log(`Server running on port ${config.port}`);
});
Validation
Validate environment variables at startup:
require('dotenv').config();
const requiredEnvVars = [
'DATABASE_URL',
'JWT_SECRET',
'API_KEY'
];
const missingVars = requiredEnvVars.filter(
varName => !process.env[varName]
);
if (missingVars.length > 0) {
console.error('Missing required environment variables:');
missingVars.forEach(varName => {
console.error(` - ${varName}`);
});
process.exit(1);
}
// Continue with app...
Type Conversion
Environment variables are always strings:
// Wrong - this is a string "3000", not a number
const PORT = process.env.PORT;
// Correct - convert to number
const PORT = parseInt(process.env.PORT, 10) || 3000;
const PORT = Number(process.env.PORT) || 3000;
// Boolean conversion
const ENABLE_FEATURE = process.env.ENABLE_FEATURE === 'true';
// Array conversion
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS?.split(',') || [];
// ALLOWED_ORIGINS=http://localhost:3000,https://example.com
Security Best Practices
Never Commit Secrets
# .gitignore
.env
.env.local
.env.*.local
.env.production
Use Different Secrets Per Environment
# Development
JWT_SECRET=dev_secret_change_in_production
# Production
JWT_SECRET=P7$mK9#xL2@nQ4&vB8!jR3^wT6
Minimal Permissions
Only give your app the environment variables it needs:
// Good - specific variables
const { DATABASE_URL, API_KEY } = process.env;
// Avoid - exposing entire process.env
app.get('/debug', (req, res) => {
res.json(process.env); // ❌ Exposes all secrets!
});
Rotate Secrets Regularly
Change API keys, database passwords, and JWT secrets periodically.
Production Deployment
Hosting Platforms
Most platforms provide environment variable management:
Heroku:
heroku config:set DATABASE_URL=postgresql://...
heroku config:set API_KEY=secret
Vercel/Netlify: Set in dashboard UI or CLI
Docker:
docker run -e DATABASE_URL=mongodb://... myapp
docker-compose.yml:
version: '3'
services:
app:
environment:
- DATABASE_URL=mongodb://mongo:27017/app
- NODE_ENV=production
Don’t Use .env in Production
Instead, set environment variables through:
- Platform UI/CLI
- CI/CD pipeline
- Container orchestration
- Secret management services (AWS Secrets Manager, etc.)
Complete Example
.env:
PORT=3000
NODE_ENV=development
DATABASE_URL=mongodb://localhost:27017/myapp
JWT_SECRET=dev_secret_key
CORS_ORIGIN=http://localhost:3000
RATE_LIMIT_MAX=100
LOG_LEVEL=debug
config.js:
require('dotenv').config();
module.exports = {
port: parseInt(process.env.PORT, 10) || 3000,
env: process.env.NODE_ENV || 'development',
database: {
url: process.env.DATABASE_URL
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: '7d'
},
cors: {
origin: process.env.CORS_ORIGIN
},
rateLimit: {
max: parseInt(process.env.RATE_LIMIT_MAX, 10) || 100,
windowMs: 15 * 60 * 1000 // 15 minutes
},
logging: {
level: process.env.LOG_LEVEL || 'info'
}
};
server.js:
const config = require('./config');
const express = require('express');
const app = express();
// Use config throughout app
app.use(express.json());
app.get('/api/status', (req, res) => {
res.json({
status: 'ok',
environment: config.env
});
});
app.listen(config.port, () => {
console.log(`Server running on port ${config.port}`);
console.log(`Environment: ${config.env}`);
});
Best Practices
✅ DO:
- Use
.envfiles for local development - Add
.envto.gitignore - Commit
.env.exampleas a template - Validate required variables at startup
- Convert string values to correct types
- Use a config module for organization
❌ DON’T:
- Commit
.envfiles with secrets - Use
.envfiles in production (use platform features) - Expose
process.envin API responses - Hard-code secrets in your code
- Forget to document required variables
Summary
| Task | Code |
|---|---|
| Load .env file | require('dotenv').config() |
| Access variable | process.env.VARIABLE_NAME |
| With fallback | process.env.PORT || 3000 |
| Convert to number | parseInt(process.env.PORT, 10) |
| Convert to boolean | process.env.VAR === 'true' |
| Check if exists | if (!process.env.VAR) { ... } |
Next Article: REST API Basics