Typed env var loader pakai Zod
Validasi semua environment variables saat startup. Error jelas kalau ada yang missing. Bonus: types di-infer otomatis.
Dipublikasikan 23 Mei 2026
process.env.DATABASE_URL selalu string | undefined, dan untuk env var integer (PORT, REDIS_DB) kita harus parseInt manual setiap kali. Snippet ini define schema sekali, validate di startup, dan dapat fully-typed env object yang aman dipakai di seluruh codebase.
Prasyarat
bun add zod
Kode
// src/env.ts
import { z } from "zod";
const envSchema = z.object({
// Required
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32, "JWT_SECRET min 32 karakter"),
NODE_ENV: z.enum(["development", "production", "test"]),
// With defaults
PORT: z.coerce.number().int().positive().default(3000),
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
// Optional
SENTRY_DSN: z.string().url().optional(),
REDIS_URL: z.string().url().optional(),
// Conditional / refined
STRIPE_KEY: z.string().regex(/^sk_(test|live)_/, "Harus mulai sk_test_ atau sk_live_"),
// Boolean dari string ("true"/"false")
ENABLE_QUEUE: z
.string()
.transform((v) => v === "true")
.default("false"),
});
function loadEnv() {
const parsed = envSchema.safeParse(process.env);
if (!parsed.success) {
console.error("❌ Environment variables tidak valid:");
console.error(parsed.error.flatten().fieldErrors);
process.exit(1);
}
return parsed.data;
}
export const env = loadEnv();
export type Env = z.infer<typeof envSchema>;
Pemakaian
// src/server.ts
import { env } from "./env";
// env.PORT itu number (sudah di-coerce dari string)
const port = env.PORT;
// env.ENABLE_QUEUE itu boolean (sudah di-transform)
if (env.ENABLE_QUEUE) {
startQueueWorker();
}
// env.SENTRY_DSN itu string | undefined — TypeScript akan complain kalau dipakai tanpa null check
if (env.SENTRY_DSN) {
initSentry(env.SENTRY_DSN);
}
// Type inference jalan — IDE autocomplete env.PORT sebagai number
Error output (saat env hilang)
❌ Environment variables tidak valid:
{
DATABASE_URL: [ 'Required' ],
JWT_SECRET: [ 'JWT_SECRET min 32 karakter' ],
STRIPE_KEY: [ 'Harus mulai sk_test_ atau sk_live_' ]
}
Kapan dipakai
- Project Bun/Node yang sudah pakai TypeScript strict.
- Saat banyak engineer yang setup local dev — error message yang jelas saving onboarding time.
- CI/CD: validate env di build step sebelum deploy. Kalau env hilang, deploy fail di sini bukan di runtime.
Catatan
- Import
envdi bagian paling awal aplikasi (src/server.tsbaris pertama). Validasi terjadi saat module pertama kali di-load. z.coerce.number()convert string env var ke number. Tanpacoerce,z.number()akan reject karena env always string.- Untuk secret yang sensitive (JWT_SECRET, encryption keys), tambahkan minimum length check supaya tidak ada yang setup dengan “test123”.
- Jangan log seluruh
envobject — secret-nya bisa bocor ke logs.
Untuk multi-environment (dev, staging, prod), pertimbangkan refined schema yang require Sentry hanya di production:
.refine( (data) => data.NODE_ENV !== "production" || !!data.SENTRY_DSN, { message: "SENTRY_DSN wajib di production", path: ["SENTRY_DSN"] } )
# tags
zodenvvalidationtype-safe