← Kembali

TypeScript Lanjut Utility

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 env di bagian paling awal aplikasi (src/server.ts baris pertama). Validasi terjadi saat module pertama kali di-load.
  • z.coerce.number() convert string env var ke number. Tanpa coerce, 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 env object — 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

← Semua snippet Snippet TypeScript lain →