Валидация с Zod
Схемы, safeParse, coerce для FormData, ошибки по полям, одна схема на клиенте и сервере.
Обновлено
Что такое валидация через схему
Данные, которые попадают в приложение (формы, query-параметры, ответы API, JSON), относятся к типу «не знаю, что это» — unknown. Валидация проверяет, что у них ожидаемая форма и правила, до того как их использовать.
Схема один раз описывает форму данных в виде объекта. Из неё вы получаете три вещи:
- валидацию во время выполнения (в браузере и на сервере);
- сообщения об ошибках по полям;
- тип TypeScript, выведенный автоматически, — писать его вручную больше не нужно.
Zod — стандартная библиотека для этого в экосистеме TypeScript.
Схема
import { z } from 'zod'
export const signupSchema = z.object({
name: z.string().trim().min(2, 'Слишком короткое имя'),
email: z.email('Неверный email'),
age: z.coerce.number().int().min(16, 'Не младше 16 лет'),
password: z.string().min(8, 'Минимум 8 символов'),
terms: z.literal('on', 'Нужно принять условия'),
})
export type SignupInput = z.infer<typeof signupSchema>
// { name: string; email: string; age: number; password: string; terms: 'on' }Базовые типы — и что они решают
| Схема | Проверяет | Заметка |
|---|---|---|
z.string(), z.number(), z.boolean() |
тип | |
z.email(), z.url(), z.uuid() |
форматы строк | в Zod 4 они на верхнем уровне; z.string().email() устарел |
.min(), .max(), .regex() |
правила | принимают сообщение вторым аргументом |
.trim(), .toLowerCase() |
преобразуют значение | результат уже очищен |
z.coerce.number() |
превращает '42' → 42 до проверки |
необходимо для FormData (там всё строки) |
z.enum(['admin', 'editor']) |
одно из значений | |
.optional(), .nullable(), .default(x) |
отсутствующие значения | |
z.array(schema), z.object({...}) |
коллекции, вложенные объекты |
parse или safeParse
schema.parse(data) |
schema.safeParse(data) |
|
|---|---|---|
| Верные данные | возвращает данные | { success: true, data } |
| Неверные данные | бросает ZodError |
{ success: false, error } — не бросает |
| Когда | данные, которые обязаны быть верными (конфиг, env) — ошибка = баг | ввод пользователя — ошибки нормальны |
const result = signupSchema.safeParse(Object.fromEntries(formData))
if (!result.success) {
return { errors: z.flattenError(result.error).fieldErrors }
// { email: ['Неверный email'], age: ['Не младше 16 лет'] }
}
await createUser(result.data) // типизировано, очищено, преобразовано| Форматирование ошибок | Результат | Когда |
|---|---|---|
z.flattenError(error) |
{ formErrors: [], fieldErrors: { email: [...] } } |
простые формы (один уровень) |
z.treeifyError(error) |
дерево по форме схемы | вложенные объекты, списки |
Правила для нескольких полей
const schema = z
.object({ password: z.string().min(8), confirm: z.string() })
.refine(d => d.password === d.confirm, { message: 'Пароли не совпадают', path: ['confirm'] })path ставит ошибку на нужное поле.
Одна схема — два места
Главный плюс: одна и та же схема на клиенте (быстрая обратная связь в форме) и на сервере (безопасность).
src/features/signup/model/signup-schema.tsсхема Zod + выведенный тип
ui/signup-form.tsxuseForm({ resolver: zodResolver(signupSchema) })
api/signup-action.ts'use server' → signupSchema.safeParse(...)
Клиентскую валидацию можно обойти в любой момент. Значение имеет серверная.
Альтернативы
| Библиотека | Почему выбрать |
|---|---|
| Zod | стандарт, огромная экосистема, отличные типы |
| Valibot | модульный API, гораздо меньший бандл для клиента |
| ArkType | синтаксис, близкий к TypeScript, очень быстрый |
Все три реализуют Standard Schema — библиотеки форм принимают их одинаково.
Коротко
- Схема = валидация + сообщения + TS-тип, написанные один раз.
safeParseдля ввода пользователя;z.coerceдляFormData;z.flattenErrorдля ошибок по полям.- Одна схема на клиенте и сервере; решает сервер.