Node.js cookbook · Beginner

Validate environment configuration at startup

Fail fast with a clear message when an env variable is missing or malformed, and get typed config.

Approach

Parse process.env once with a Zod schema at startup, coerce types (numbers, booleans, URLs), provide defaults, and export a frozen, typed config object. Never read process.env elsewhere. Node 20.6+ can load .env files natively with --env-file.

Solution

// src/config.ts
import { z } from "zod";

const Env = z.object({
  NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
  PORT: z.coerce.number().int().min(1).max(65535).default(3000),
  DATABASE_URL: z.string().url(),
  REDIS_URL: z.string().url(),
  JWT_AUDIENCE: z.string().min(1),
  CORS_ORIGINS: z.string().transform((s) => s.split(",").map((o) => o.trim())).default(""),
  LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
});

const parsed = Env.safeParse(process.env);
if (!parsed.success) {
  console.error("Invalid configuration:", parsed.error.flatten().fieldErrors);
  process.exit(1);
}

export const config = Object.freeze(parsed.data);
export type Config = typeof config;
node --env-file=.env dist/server.js      # Node 20.6+ loads .env natively

Pitfalls

  • process.env.PORT used as a string number.
  • Logging the config object with secrets.
  • Defaults for secrets (fail instead).

Variations

  • @t3-oss/env-core for client/server env separation in full-stack apps.

Further reading: Node.js: --env-file

Reference: https://nodejs.org/api/cli.html#--env-fileconfig

More Node.js recipes