•9 min read

TypeScript thôi là chưa đủ: Đảm bảo an toàn kiểu dữ liệu end-to-end với Zod trong Next.js

TypeScript thôi là chưa đủ: Đảm bảo an toàn kiểu dữ liệu end-to-end với Zod trong Next.js
Audio Briefing
0:00 / 0:00

Ảo ảnh về An toàn kiểu dữ liệu (Type Safety)

TypeScript thường được mô tả là vị cứu tinh của các ứng dụng JavaScript hiện đại. Nó giúp bắt lỗi chính tả, tự động hoàn thành thuộc tính đối tượng và ngăn chặn việc gọi các hàm không xác định trong quá trình phát triển.

Tuy nhiên, hàng trăm ứng dụng Next.js và Node.js đang chạy trên môi trường production vẫn gặp lỗi mỗi ngày với: TypeError: Cannot read properties of undefined (reading 'map') hoặc TypeError: data.slice is not a function.

Tại sao điều này lại xảy ra mặc dù đã bật các cài đặt trình biên dịch TypeScript nghiêm ngặt?

Bởi vì TypeScript không tồn tại ở thời điểm chạy (runtime). Khi mã của bạn biên dịch, mọi interface, type alias, generic parameter và type assertion (as User) đều bị xóa khỏi gói JavaScript. Tại ranh giới mạng—các yêu cầu HTTP đến, payload của Next.js Server Action, sự kiện webhook của bên thứ ba và kết quả truy vấn cơ sở dữ liệu—dữ liệu chạy qua mã của bạn hoàn toàn không có kiểu.

Để đạt được độ tin cậy thực sự từ đầu đến cuối, bạn phải xác thực dữ liệu khi nó đi vào hệ thống của mình. Đó là lúc Zod trở nên thiết yếu.

Cần chuyển đổi ngay lập tức JSON API thô thành các interface TypeScript và Zod schema? Hãy sử dụng công cụ hỗ trợ của chúng tôi JSON to TypeScript & Zod Generator Tool.


Advertisement

Kiểu tĩnh (Static Types) so với Schema thời gian chạy (Runtime Schemas)

Hiểu được sự khác biệt cơ bản giữa các interface TypeScript và Zod schema là chìa khóa để xây dựng các kiến trúc mạnh mẽ:

Khía cạnhTypeScript InterfaceZod Runtime Schema
Giai đoạn vòng đời
Chỉ ở thời điểm biên dịch (bị xóa trong quá trình build)
Thực thi ở thời điểm chạy (hoạt động trong mọi yêu cầu trực tiếp)
Bảo vệ ranh giới mạng
Không có bảo vệ; tin tưởng mù quáng vào việc ép kiểu dữ liệu (ví dụ: body as RequestBody)
Bảo vệ hoàn toàn; từ chối các payload bị lỗi và các injection độc hại trước khi thực thi
Kích thước gói (Bundle Footprint)
0 kB thêm vào gói (chỉ là siêu dữ liệu kiểu thuần túy)
Thư viện parser thời gian chạy nhỏ ~12 kB
Nguồn chân lý duy nhất (Single Source of Truth)
Yêu cầu đồng bộ hóa thủ công nếu bạn cũng viết các trình xác thực thời gian chạy
Tự động suy luận các kiểu TypeScript thông qua z.infer<typeof Schema>

Tái cấu trúc Server Actions dễ bị tấn công sang Zod

Hãy xem một lỗ hổng phổ biến trong Next.js Server Actions nơi các nhà phát triển sử dụng type assertion mà không có xác thực thời gian chạy:


4 Kỹ thuật mạnh mẽ để sử dụng Zod nâng cao

1. z.coerce cho Dữ liệu Form và Tham số tìm kiếm URL

Khi phân tích cú pháp HTML FormData hoặc Next.js searchParams, mọi thứ đều đến dưới dạng chuỗi ("42", "true"). Thay vì viết các lệnh gọi parseInt() hoặc Boolean() thủ công:

const FilterSchema = z.object({
  page: z.coerce.number().default(1),
  limit: z.coerce.number().default(20),
  active: z.coerce.boolean().default(true),
});

2. safeParse so với parse

  • schema.parse(data) ném ra một ZodError nếu xác thực thất bại. Chỉ sử dụng điều này bên trong các khối try/catch hoặc các trình xử lý API cấp cao nhất với middleware xử lý lỗi toàn cục.
  • schema.safeParse(data) không bao giờ ném lỗi. Nó trả về một union phân biệt: { success: true, data } hoặc { success: false, error }. Điều này lý tưởng cho Next.js Server Actions và React Hook Form.

3. Kết hợp và Kế thừa Schema

Các Zod schema có thể kết hợp. Bạn có thể mở rộng, hợp nhất và chọn các trường:

const BaseUserSchema = z.object({
  id: z.string().uuid(),
  createdAt: z.date(),
});

const UpdateProfileSchema = BaseUserSchema.extend({
  bio: z.string().max(500).optional(),
  displayName: z.string().min(2),
});

4. Tinh chỉnh tùy chỉnh với .refine()

Để xác thực chéo các trường (chẳng hạn như đảm bảo "xác nhận mật khẩu" khớp với "mật khẩu"):

const PasswordChangeSchema = z.object({
  newPassword: z.string().min(8),
  confirmPassword: z.string().min(8),
}).refine((data) => data.newPassword === data.confirmPassword, {
  message: "Passwords do not match",
  path: ["confirmPassword"],
});

Advertisement

Câu đố tương tác: Kiểm tra kiến thức Zod của bạn


Bạn cũng có thể thích

Các câu hỏi thường gặp

Trong các micro-benchmark, Zod xác thực các payload JSON điển hình trong một phần nhỏ của mili giây (dưới 0.1ms cho các đối tượng tiêu chuẩn). Chi phí CPU không đáng kể được bù đắp hoàn toàn bằng cách ngăn chặn việc hỏng cơ sở dữ liệu âm thầm và các sự cố máy chủ thảm khốc.
Không bao giờ trùng lặp kiểu thủ công. Định nghĩa Zod schema của bạn là nguồn chân lý duy nhất, sau đó sử dụng z.infer<typeof Schema> để tự động suy ra các kiểu TypeScript tĩnh của bạn. Điều này đảm bảo rằng xác thực thời gian chạy và kiểm tra thời gian biên dịch của bạn không bao giờ bị lệch nhau.
Bạn có thể sử dụng công cụ trực tuyến miễn phí của chúng tôi JSON to TypeScript & Zod Generator để dán bất kỳ payload JSON nào và ngay lập tức nhận được các interface TypeScript được định dạng và các Zod schema sẵn sàng sử dụng chỉ với một cú nhấp chuột.
Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement