Xác thực biến môi trường type-safe với Zod

Đăng ngày

Với nhiều developer mới bắt đầu, cấu hình secrets và settings thường khởi đầu bằng file .env đơn giản, nhờ các thư viện như dotenv. Nghe là thấy tiện ngay: vừa giữ thông tin nhạy cảm khỏi codebase, vừa cấu hình ứng dụng theo từng môi trường. Muốn lấy biến thì cứ đọc từ object global process.env là xong.

const apiKey = process.env.API_KEY;

Tuy nhiên, khi dự án lớn dần, sự đơn giản này dễ biến thành lỗi thầm lặng và đau đầu lúc deploy. process.env vốn không có kiểu, nên rất dễ bị lỗi typo, thiếu biến ở môi trường quan trọng, rồi phải validate thủ công từng cái một.

Ví dụ API_KEY là thứ ứng dụng bắt buộc phải có. Không check rõ ràng thì đọc nó có thể ra undefined, rồi gặp lỗi runtime khó hiểu, mất cả buổi debug. Developer phải tự nhớ các bước check này, và phải làm đều đặn mọi nơi.

const apiKey = process.env.API_KEY;
if (!apiKey) {
	throw new Error('API_KEY is not set in the environment.');
}

Chưa kể, quản lý các biến này xuyên suốt nhiều giai đoạn deploy (development, staging, production) cũng dễ rối. Sửa file .env ở máy local thì dễ; nhớ cập nhật tương ứng trên môi trường deploy lại là chuyện khác.

Schema-based validation lên ngôi

Quản lý biến môi trường the hướng type-safe đang ngày càng phổ biến, điển hình là Zod (hoặc Yup). Thay vì đọc string không kiểu, bạn định nghĩa một schema rõ ràng, chặt chẽ cho toàn bộ biến môi trường.

Cùng xem ví dụ với Zod:

import { z } from 'zod';

const envSchema = z.object({
	GISCUS_REPO: z.string(),
	GISCUS_REPO_ID: z.string(),
	GISCUS_CATEGORY: z.string(),
	GISCUS_CATEGORY_ID: z.string()
});

const loadEnv = () => {
	try {
		// LƯU Ý: Cách truy cập process.env sẽ khác nhau tùy dự án!
		const validatedEnv = envSchema.parse(process.env);
		return validatedEnv;
	} catch (error) {
		if (error instanceof z.ZodError) {
			console.error('❌ Invalid environment variables:', error.flatten().fieldErrors);
			// Tùy ngữ cảnh, bạn có thể throw error,
			// trả về object mặc định, hoặc dừng process.
			// Ở đây mình throw lại để dừng chạy nếu validate fail nặng.
			throw new Error('Invalid environment variables');
		}
		throw error; // Throw lại các error không ngờ khác
	}
};

Từ đây, thay vì đụng thẳng process.env, bạn gọi loadEnv(). Hàm này parse biến môi trường theo envSchema đã định nghĩa. Thiếu biến bắt buộc hoặc sai kiểu so với schema (ví dụ string trong khi cần number), Zod sẽ throw một error chi tiết.

Lợi ích cụ thể: rõ ràng và chắc chắn hơn

Cách này có vài điểm đáng kể:

  • Vừa rõ, vừa là documentation: envSchema chính là tài liệu sống về các biến môi trường ứng dụng cần. Nhìn schema là biết cần cấu hình gì, kiểu dữ liệu ra sao. Bạn còn có thể dùng khả năng validate của Zod để siết rule phức tạp hơn, như min/max cho number hay format cụ thể cho string.

    const complexEnvSchema = z.object({
    	PORT: z.number().min(1000).max(9999),
    	API_URLS: z.array(z.string().url()).length(3),
    	LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info')
    });
  • Bắt lỗi sớm: Validate ngay lúc ứng dụng startup sẽ lộ lỗi cấu hình từ sớm, không chờ tới runtime hay production. Debug ít hơn, app cũng ổn định hơn.

  • Developer experience tốt hơn: Có schema rồi, IDE thường gợi ý autocompletion và type hinting tốt hơn cho biến môi trường sau khi đã validate. Viết code mượt hơn, ít sai hơn.

Bài học từ sai lầm thực tế của mình

Mình thấy rõ lợi ích này khi vừa deploy blog cá nhân lên Netlify. Quên cấu hình Giscus trong biến môi trường, deployment pipeline báo lỗi ngay. Feedback nhanh đó tiết kiệm thời gian mà lẽ ra mình phải mất để debug một feature đang live nhưng bị hỏng. Làm theo cách này, bạn cũng có thể tránh được những tình huống tương tự.

Deployment pipeline báo lỗi ngay lập tức

Kết luận

Không dừng ở file .env cơ bản, dùng schema để validate biến môi trường là bước khá quan trọng nếu muốn app vững hơn, dễ maintain hơn, và dễ làm việc hơn với developer. Zod giúp đưa type safety và sự rõ ràng vào phần cấu hình — ít bất ngờ hơn, develop và deploy cũng mượt hơn. Đây là cách làm càng có ích về lâu dài, nhất là khi dự án lớn dần về quy mô lẫn độ phức tạp.