Managing .env and Environment Variables in Node.js

THEJORD Team1 min read
nodejsenvconfigurationsecurity

How to use environment variables in Node.js: dotenv, .env files, security. Best practices.

Managing .env and Environment Variables in Node.js

Introduction to Environment Variables

Environment variables are key-value pairs that configure your application's behavior without hardcoding values in your source code. They're essential for managing secrets, API keys, and environment-specific configuration. This guide covers best practices for using .env files and environment variables in Node.js applications.

Why Use Environment Variables?

Security

  • Keep secrets out of source code
  • Never commit API keys to Git
  • Different credentials per environment

Flexibility

  • Same code in dev, staging, production
  • Easy configuration changes without deployment
  • Container and cloud-native deployment

The .env File

Basic Syntax

# .env file
# Comments start with #

DATABASE_URL=postgres://user:pass@localhost:5432/mydb
API_KEY=sk-abc123def456
PORT=3000
DEBUG=true
NODE_ENV=development

# Strings with spaces need quotes
APP_NAME="My Application"

# Multi-line values
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY-----"

File Naming Conventions

.env                 # Default, loaded in all environments
.env.local           # Local overrides (git-ignored)
.env.development     # Development-specific
.env.production      # Production-specific
.env.test            # Test-specific
.env.example         # Template for required variables

Using dotenv in Node.js

Installation

npm install dotenv

Basic Usage

// Load at the top of your entry file
import 'dotenv/config';
// or
require('dotenv').config();

// Access variables
const port = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL;
const apiKey = process.env.API_KEY;

Conditional Loading

import dotenv from 'dotenv';

// Load different files based on NODE_ENV
const envFile = `.env.${process.env.NODE_ENV || 'development'}`;
dotenv.config({ path: envFile });

// Override with local file
dotenv.config({ path: '.env.local', override: true });

Type Safety with TypeScript

Type Definitions

// types/env.d.ts
declare namespace NodeJS {
  interface ProcessEnv {
    NODE_ENV: 'development' | 'production' | 'test';
    PORT: string;
    DATABASE_URL: string;
    API_KEY: string;
  }
}

Validation with Zod

import { z } from 'zod';

const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']),
  PORT: z.string().transform(Number),
  DATABASE_URL: z.string().url(),
  API_KEY: z.string().min(1),
});

// Validate and parse
const env = envSchema.parse(process.env);

// Use typed env
console.log(env.PORT); // number
console.log(env.DATABASE_URL); // string

Framework Integration

Next.js

// .env.local (automatically loaded)
DATABASE_URL=postgres://...

// Public variables (exposed to browser)
NEXT_PUBLIC_API_URL=https://api.example.com

// Usage in code
const apiUrl = process.env.NEXT_PUBLIC_API_URL;  // Client-safe
const dbUrl = process.env.DATABASE_URL;           // Server-only

Vite

// .env
VITE_API_URL=https://api.example.com

// Usage (only VITE_ prefixed vars are exposed)
const apiUrl = import.meta.env.VITE_API_URL;

Express

// config.js
import 'dotenv/config';

export const config = {
  port: parseInt(process.env.PORT) || 3000,
  dbUrl: process.env.DATABASE_URL,
  isProduction: process.env.NODE_ENV === 'production',
  logLevel: process.env.LOG_LEVEL || 'info',
};

// app.js
import { config } from './config.js';
app.listen(config.port);

Security Best Practices

.gitignore

# Always ignore .env files with secrets
.env
.env.local
.env.*.local

# Keep example file
!.env.example

.env.example Template

# .env.example (commit this to repo)
# Copy to .env and fill in values

DATABASE_URL=postgres://user:password@localhost:5432/dbname
API_KEY=your-api-key-here
PORT=3000
NODE_ENV=development

Secret Rotation

// Support multiple API keys during rotation
const apiKeys = process.env.API_KEYS?.split(',') || [];

function validateApiKey(key) {
  return apiKeys.includes(key);
}

Production Deployment

Docker

# Dockerfile - don't include .env
FROM node:20
COPY . .
RUN npm install
CMD ["node", "server.js"]

# docker-compose.yml
services:
  app:
    build: .
    env_file:
      - .env.production
    environment:
      - NODE_ENV=production

Cloud Platforms

# Vercel
vercel env add DATABASE_URL production

# Heroku
heroku config:set DATABASE_URL=postgres://...

# AWS (via Parameter Store or Secrets Manager)
aws ssm put-parameter --name "/app/DATABASE_URL" --value "..." --type SecureString

Common Patterns

Default Values

const port = process.env.PORT || 3000;
const host = process.env.HOST ?? 'localhost';
const debug = process.env.DEBUG === 'true';

Required Variables

function requireEnv(name) {
  const value = process.env[name];
  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }
  return value;
}

const apiKey = requireEnv('API_KEY');

Parsing Complex Values

# .env
ALLOWED_ORIGINS=http://localhost:3000,https://example.com
FEATURE_FLAGS={"darkMode":true,"betaFeatures":false}

// JavaScript
const origins = process.env.ALLOWED_ORIGINS?.split(',') || [];
const features = JSON.parse(process.env.FEATURE_FLAGS || '{}');

Tools and Resources

For working with configuration:

Troubleshooting

Variables Not Loading

// Check if dotenv is loaded first
console.log(process.env.MY_VAR); // undefined

// Solution: import dotenv before other imports
import 'dotenv/config';
import { myModule } from './module.js';

Different Values in Production

// Debug: log which file is loaded
console.log('NODE_ENV:', process.env.NODE_ENV);
console.log('Loading from:', `.env.${process.env.NODE_ENV}`);

Conclusion

Environment variables are essential for secure, flexible applications. Key takeaways:

  • Never commit secrets to source control
  • Use .env files for local development
  • Validate environment variables at startup
  • Use platform-specific secret management in production
  • Keep .env.example updated for team onboarding

For more developer resources, explore our free online tools. For dotenv documentation, see dotenv on GitHub.