Skip to content

23 Environment Config

DodaTech 4 min read

title: Environment Configuration in Node.js REST APIs weight: 33 date: 2026-06-28 lastmod: 2026-06-28 description: Manage environment configuration in Node.js REST APIs using dotenv, config files per environment, environment validation, and secure secret management. tags: [api-development, nodejs]


Environment configuration in Node.js REST APIs manages settings across development, testing, staging, and production using .env files, environment-specific config files, validation schemas, and secure secret management.

```mermaid
flowchart TD
  A[Configuration] --> B[.env Files]
  A --> C[Environment-specific]
  A --> D[Secret Management]
  B --> E[.env.development]
  B --> F[.env.production]
  C --> G[config/development.js]
  C --> H[config/production.js]
  D --> I[Vault / AWS Secrets]
  style A fill:#e1f5fe
  style B fill:#c8e6c9
  style D fill:#fff9c4

Store configuration in environment variables accessed through process.env. Use the dotenv package to load .env files in development. Validate configuration at startup to catch missing variables early. Never commit .env files to version control.

Think of environment configuration like a hotel room key. The key works differently depending on where you are: at the front desk (development), on your floor (staging), or in the executive suite (production). The key knows which doors to open based on context.

Example: Environment Configuration Module

const dotenv = require('dotenv');
const path = require('path');

// Load environment-specific .env file
const envFile = process.env.NODE_ENV || 'development';
dotenv.config({ path: path.resolve(process.cwd(), `.env.${envFile}`) });

// Also load .env for common variables
dotenv.config();

const config = {
  env: envFile,
  port: parseInt(process.env.PORT, 10) || 3000,
  database: {
    url: process.env.DATABASE_URL,
    host: process.env.DB_HOST || 'localhost',
    port: parseInt(process.env.DB_PORT, 10) || 27017,
    name: process.env.DB_NAME || 'myapp'
  },
  jwt: {
    secret: process.env.JWT_SECRET,
    refreshSecret: process.env.JWT_REFRESH_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN || '15m',
    refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '7d'
  },
  redis: {
    url: process.env.REDIS_URL || 'redis://localhost:6379'
  },
  rateLimit: {
    windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) || 900000,
    max: parseInt(process.env.RATE_LIMIT_MAX, 10) || 100
  },
  cors: {
    origin: process.env.CORS_ORIGIN || '*'
  },
  log: {
    level: process.env.LOG_LEVEL || 'info'
  }
};

module.exports = config;

Example: Configuration Validation

const config = require('./config');

const requiredVars = [
  'DATABASE_URL',
  'JWT_SECRET',
  'JWT_REFRESH_SECRET'
];

const validateConfig = () => {
  const missing = [];

  for (const varName of requiredVars) {
    if (!process.env[varName]) {
      missing.push(varName);
    }
  }

  if (missing.length > 0) {
    throw new Error(
      `Missing required environment variables: ${missing.join(', ')}\n` +
      `Check your .env.${process.env.NODE_ENV || 'development'} file.`
    );
  }

  // Validate URLs
  try {
    new URL(config.database.url);
  } catch {
    throw new Error('DATABASE_URL must be a valid URL');
  }

  // Validate numeric values
  if (isNaN(config.port) || config.port < 1 || config.port > 65535) {
    throw new Error('PORT must be a valid port number (1-65535)');
  }

  console.log(`Configuration validated for ${config.env} environment`);
};

validateConfig();

module.exports = config;

Example: Environment Files

# .env.development
NODE_ENV=development
PORT=3000
DATABASE_URL=mongodb://localhost:27017/myapp_dev
REDIS_URL=redis://localhost:6379
JWT_SECRET=dev-secret-do-not-use-in-production
JWT_REFRESH_SECRET=dev-refresh-secret
LOG_LEVEL=debug
CORS_ORIGIN=http://localhost:3001
# .env.production
NODE_ENV=production
PORT=3000
DATABASE_URL=mongodb://mongo:27017/myapp
REDIS_URL=redis://redis:6379
JWT_SECRET=${JWT_SECRET}
JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET}
LOG_LEVEL=info
CORS_ORIGIN=https://myapp.com
RATE_LIMIT_MAX=100

Common Mistakes

  1. Committing .env files to version control — .env files often contain secrets. Add .env to .gitignore. Use .env.example as a template.
  2. Hard-coding configuration values — Hard-coded values require code changes for different environments. Always use environment variables.
  3. Not validating configuration at startup — Missing required config causes cryptic runtime errors instead of clear startup failures. Validate early.
  4. Using the same secret across environments — Development secrets should differ from production secrets. Compromised dev secrets should not affect production.
  5. Storing secrets in config files — Even encrypted config files can be decrypted. Use a secret manager (Vault, AWS Secrets Manager) for production secrets.

Practice Questions

  1. Why should you never commit .env files to version control?
  2. How do you load different configuration for different environments?
  3. What is the purpose of configuration validation at startup?
  4. How do you handle secret management in production?
  5. Challenge: Build a configuration system that loads environment-specific .env files, validates all required variables, provides typed accessors (port as number, features as boolean), and supports a secrets manager integration for production.

FAQ

What is the 12-factor app approach to configuration?

Store config in environment variables. Never group config as constants in code. Environment variables are language and OS agnostic.

Should I use YAML config files or .env?

.env files are simpler and follow the 12-factor app methodology. YAML files allow structured config but can become complex.

How do I handle secrets in CI/CD pipelines?

Use CI/CD secret variables (GitHub Actions secrets, GitLab CI variables). Inject them as environment variables during deployment.

What is the difference between .env and .env.example?

.env contains actual values (possibly secrets). .env.example contains placeholder values and serves as a template for developers.

How do I handle feature flags through configuration?

Add boolean environment variables like FEATURE_NEW_CHECKOUT=true. Use a feature flag service for more complex scenarios.

Mini Project

Build a configuration system for a Node.js REST API. Include: environment-specific .env files, config validation at startup, typed configuration accessors, a secrets manager integration (using environment variables), and a .env.example template. Test that the app fails fast when required config is missing.

What's Next

Now learn about security best practices in Building REST APIs with Node.js.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro