Zod TypeScript Validation

NPM: zod

Что это такое?

Zod - это TypeScript-first библиотека для runtime-валидации данных. TypeScript проверяет типы только во время разработки, а Zod проверяет реальные данные во время выполнения программы.

Особенно полезен для:

  • данных из API;
  • переменных окружения;
  • форм;
  • query-параметров и route params;
  • сообщений из очередей, WebSocket и webhooks;
  • контрактов между frontend и backend.

Установка

pnpm add zod

Базовая схема

import { z } from 'zod';
 
const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  age: z.number().int().positive().optional(),
  role: z.enum(['admin', 'user']),
});
 
type User = z.infer<typeof UserSchema>;

UserSchema используется в runtime, а User получается из схемы и используется как обычный TypeScript-тип.

parse и safeParse

parse выбрасывает ошибку, если данные невалидны.

const user = UserSchema.parse(input);

safeParse возвращает объект с результатом и удобнее для контролируемой обработки ошибок.

const result = UserSchema.safeParse(input);
 
if (!result.success) {
  console.log(result.error.flatten());
  return;
}
 
const user = result.data;

Валидация env

import { z } from 'zod';
 
const EnvSchema = z.object({
  NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
  DATABASE_URL: z.string().url(),
  PORT: z.coerce.number().int().positive().default(3000),
});
 
export const env = EnvSchema.parse(process.env);

z.coerce.number() полезен, потому что значения из process.env всегда приходят строками.

Валидация API-ответа

const ProductSchema = z.object({
  id: z.string(),
  title: z.string(),
  price: z.number().nonnegative(),
});
 
const ProductsResponseSchema = z.array(ProductSchema);
 
async function getProducts() {
  const response = await fetch('/api/products');
  const json = await response.json();
 
  return ProductsResponseSchema.parse(json);
}

Так приложение не доверяет внешнему API вслепую.

refine и transform

refine добавляет кастомную проверку.

const PasswordSchema = z
  .string()
  .min(8)
  .refine((value) => /\d/.test(value), 'Пароль должен содержать цифру');

transform преобразует валидное значение.

const SlugSchema = z.string().transform((value) => value.trim().toLowerCase());

Когда выбирать Zod

Zod стоит брать по умолчанию, если нужен понятный DX, хорошая поддержка TypeScript и один источник правды для схемы и типа.

Не стоит превращать Zod в замену бизнес-логике. Схемы должны проверять форму данных, а правила вроде “пользователь может оплатить заказ” лучше держать в доменной логике.

Плюсы

  • отличная TypeScript-интеграция и вывод типов из схем;
  • удобный API для форм, API-ответов и env;
  • большая экосистема интеграций.

Минусы

  • runtime-валидация добавляет вес и стоимость выполнения;
  • сложные схемы могут стать трудно читаемыми;
  • не заменяет доменную бизнес-валидацию.

Когда использовать

  • нужно валидировать неизвестные данные на границах системы;
  • нужен один источник правды для схемы и TypeScript-типа;
  • проекту важнее DX и экосистема, чем минимальный размер бандла.