Environment variable management and secret rotation

Ryan Nakamura Feb 2026
2 tabs
// Configuration management with validation
const Joi = require('joi');

// Define schema for all environment variables
const envSchema = Joi.object({
  NODE_ENV: Joi.string()
    .valid('development', 'staging', 'production', 'test')
    .default('development'),

  PORT: Joi.number().default(3000),
  HOST: Joi.string().default('0.0.0.0'),

  // Database
  DATABASE_URL: Joi.string().uri().required(),
  DB_POOL_MIN: Joi.number().default(2),
  DB_POOL_MAX: Joi.number().default(10),

  // Redis
  REDIS_URL: Joi.string().uri().required(),

  // Auth
  JWT_SECRET: Joi.string().min(32).required(),
  JWT_EXPIRY: Joi.string().default('24h'),

  // AWS
  AWS_REGION: Joi.string().default('us-east-1'),
  S3_BUCKET: Joi.string().required(),

  // External APIs
  STRIPE_SECRET_KEY: Joi.string().when('NODE_ENV', {
    is: 'production',
    then: Joi.required(),
    otherwise: Joi.optional(),
  }),

  // Feature flags
  ENABLE_CACHE: Joi.boolean().default(true),
  ENABLE_RATE_LIMIT: Joi.boolean().default(true),

  // Logging
  LOG_LEVEL: Joi.string()
    .valid('error', 'warn', 'info', 'debug')
    .default('info'),
})
  .unknown(true); // Allow other env vars

// Validate and export
const { error, value: config } = envSchema.validate(process.env, {
  abortEarly: false,
});

if (error) {
  const missing = error.details.map((d) => d.message).join('\n  ');
  console.error(`Configuration validation error:\n  ${missing}`);
  process.exit(1);
}

module.exports = {
  env: config.NODE_ENV,
  isProduction: config.NODE_ENV === 'production',
  isDevelopment: config.NODE_ENV === 'development',

  server: {
    port: config.PORT,
    host: config.HOST,
  },

  database: {
    url: config.DATABASE_URL,
    pool: {
      min: config.DB_POOL_MIN,
      max: config.DB_POOL_MAX,
    },
  },

  redis: {
    url: config.REDIS_URL,
  },

  auth: {
    jwtSecret: config.JWT_SECRET,
    jwtExpiry: config.JWT_EXPIRY,
  },

  aws: {
    region: config.AWS_REGION,
    s3Bucket: config.S3_BUCKET,
  },

  features: {
    cache: config.ENABLE_CACHE,
    rateLimit: config.ENABLE_RATE_LIMIT,
  },

  logging: {
    level: config.LOG_LEVEL,
  },
};
2 files · javascript, bash Explain with highlit

Environment variables configure applications without code changes. The twelve-factor app methodology stores config in the environment. .env files provide local defaults—never commit them to Git. dotenv libraries load .env files in development. Production secrets live in AWS Secrets Manager, HashiCorp Vault, or Kubernetes Secrets. Secret rotation updates credentials without downtime using dual-read patterns. AWS Secrets Manager supports automatic rotation with Lambda functions. Connection pooling libraries handle credential refresh transparently. Config validation at startup fails fast on missing variables. Environment-specific config files (production.env, staging.env) separate concerns. envsubst substitutes variables in templates. Secret scanning in CI prevents accidental credential commits.