Type-Safe Environment Variables in Node.js With Zod
Missing environment variables crash apps in production. Undocumented variables create confusion. Here's how to validate and type your env vars at startup.
The Problem
// This compiles fine but crashes at runtime if DATABASE_URL is missing
const db = new PrismaClient({ url: process.env.DATABASE_URL });
// TypeError: Cannot read property of undefined
Validate With Zod at Startup
// env.ts — load this before anything else
import { z } from 'zod';
const envSchema = z.object({
// Database
DATABASE_URL: z.string().url(),
REDIS_URL: z.string().url(),
// Auth
NEXTAUTH_SECRET: z.string().min(32),
NEXTAUTH_URL: z.string().url(),
// Stripe
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
STRIPE_WEBHOOK_SECRET: z.string().startsWith('whsec_'),
// App
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
PORT: z.coerce.number().default(3000),
// Optional
SENTRY_DSN: z.string().url().optional(),
});
const result = envSchema.safeParse(process.env);
if (!result.success) {
console.error('Invalid environment variables:');
console.error(result.error.flatten().fieldErrors);
process.exit(1); // Fail fast — don't start with invalid config
}
export const env = result.data;
// env is fully typed: env.PORT is number, env.DATABASE_URL is string
Using the Typed Env
import { env } from './env';
const db = new PrismaClient({ url: env.DATABASE_URL }); // typed, validated
const port = env.PORT; // number, not string
T3 Env: The Library Version
npm install @t3-oss/env-nextjs
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';
export const env = createEnv({
server: {
DATABASE_URL: z.string().url(),
STRIPE_SECRET_KEY: z.string(),
},
client: {
NEXT_PUBLIC_APP_URL: z.string().url(),
},
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
},
});
.env.example as Documentation
# .env.example — commit this, not .env
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
REDIS_URL=redis://localhost:6379
NEXTAUTH_SECRET=generate-with-openssl-rand-base64-32
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
Type-safe env validation, a complete .env.example, and startup checks are pre-configured in the AI SaaS Starter Kit.
Top comments (0)