Validate ENV Variables in TypeScript with Zod

November, 7th 2024 2 min read

Validating environment variables in TypeScript is essential for preventing runtime failures caused by missing or misconfigured values. Zod offers a clean, type‑safe way to verify all required variables before your app starts.

How to Validate ENV Variables in TypeScript Using Zod

Why ENV Validation Matters

Configuration errors are among the most common causes of production crashes. By validating ENV variables at startup, you ensure:

  • Required values exist
  • Types are correct
  • URLs, ports, keys, and modes match expectations
  • Your app fails early, not unpredictably at runtime

Zod makes this process extremely reliable and fully typed across the entire project.


Step 1: Install Dependencies

bash
npm install dotenv zod

Load variables before running validation:

ts
import dotenv from "dotenv";
dotenv.config();

Step 2: Create a Strongly Typed Zod Schema

ts
import { z } from "zod";

export const envSchema = z.object({
  NODE_ENV: z.enum(["development", "production", "test"]),
  PORT: z.string().transform(Number).refine(p => p > 1000 && p < 5000, {
    message: "PORT must be a number between 1000 and 5000",
  }),
  DATABASE_URL: z.string().url(),
  AUTH_SECRET: z.string().min(1, "AUTH_SECRET must not be empty"),
});

Improvements in this version

  • Better numeric comparison
  • Clearer messages
  • min(1) used to catch empty strings
  • Schema exported for global typing

Step 3: Validate and Export Typed ENV Values

ts
const parsed = envSchema.safeParse(process.env);

if (!parsed.success) {
  console.error("❌ Invalid ENV configuration:");
  console.error(parsed.error.flatten().fieldErrors);
  process.exit(1);
}

export const env = parsed.data;

Benefits

  • safeParse avoids throwing errors
  • Graceful shutdown on misconfiguration
  • Fully typed env object for safe use everywhere

Step 4: Add Global Types for process.env

Create: src/types/env.d.ts

ts
import { envSchema } from "../env";
import { z } from "zod";

declare global {
  namespace NodeJS {
    interface ProcessEnv extends z.infer<typeof envSchema> {}
  }
}

Now TypeScript warns you about typos or missing variables.


Full Example

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

dotenv.config();

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

const parsed = envSchema.safeParse(process.env);

if (!parsed.success) {
  console.error("ENV validation failed:", parsed.error.format());
  process.exit(1);
}

export const env = parsed.data;

Best Practices for ENV Validation

  • Never trust runtime values — always validate
  • Use .min(1) for secrets to avoid empty strings
  • Use .url() for database/storage endpoints
  • Add default values only when truly optional
  • Fail fast in production environments

Alternatives

  • Joi — powerful but less TypeScript‑friendly
  • Valibot — lightweight validator
  • Runtypes — similar runtime validation
  • dotenv‑typesafe — simpler but less flexible

Conclusion

Validating environment variables with Zod brings:

  • Safety
  • Clear errors
  • Predictable startup behavior
  • Full type inference across your app

This small setup prevents major runtime bugs and ensures your configuration stays clean and reliable.