Zod Nested Environment Config

Flatten process.env into a strongly typed nested config with Zod, supporting defaults, optional sections, and fail-fast startup checks.

TypescriptNodeConfig Management

Code

snippet
import { z } from 'zod';

const ConfigSchema = z.object({
  NODE_ENV: z.enum(['development', 'staging', 'production']),
  PORT: z.coerce.number().int().min(1).max(65535).default(3000),

  DATABASE_URL: z.string().url(),
  DATABASE_POOL_MAX: z.coerce.number().int().min(1).max(100).default(10),

  REDIS_URL: z.string().url().optional(),

  FEATURES: z.string().transform((raw, ctx) => {
    const flags = raw.split(',').map((f) => f.trim()).filter(Boolean);
    const known = ['audit', 'beta-ui', 'webhooks'] as const;
    for (const f of flags) {
      if (!known.includes(f as never)) {
        ctx.addIssue({ code: 'custom', message: 'unknown feature flag: ' + f });
        return z.NEVER;
      }
    }
    return flags;
  }).default(''),
});

type AppConfig = z.infer<typeof ConfigSchema>;

function loadConfig(source: Record<string, string | undefined>): AppConfig {
  const parsed = ConfigSchema.safeParse(source);
  if (!parsed.success) {
    const report = parsed.error.issues
      .map((i) => i.path.join('.') + ': ' + i.message)
      .join('; ');
    throw new Error('invalid environment configuration -> ' + report);
  }
  return parsed.data;
}

export const config = loadConfig(process.env as Record<string, string | undefined>);
export const isProd = config.NODE_ENV === 'production';