Одной из ключевых идей при работе с Zod является построение валидаторов как композиции небольших, независимых и повторно используемых блоков. Схемы перестают быть одноразовыми описаниями структуры данных и превращаются в доменные строительные элементы.
Переиспользуемый валидатор — это схема или функция, возвращающая схему, которая используется в разных частях приложения без дублирования логики валидации.
Основные цели:
В Zod каждый примитив может стать частью общей библиотеки валидаторов:
import { z } from "zod";
export const email = z.string().email();
export const password = z.string().min(8).max(64);
export const uuid = z.string().uuid();
Такие определения становятся фундаментом для более сложных схем.
Использование:
const userSchema = z.object({
email,
password,
});
Частая проблема — расхождение ограничений между различными схемами. Решение — вынесение параметров в отдельные константы:
const PASSWORD_MIN = 8;
const PASSWORD_MAX = 64;
export const password = z.string()
.min(PASSWORD_MIN)
.max(PASSWORD_MAX);
Преимущество подхода:
Когда валидация зависит от параметров, используются функции, возвращающие схемы.
export const minMaxString = (min: number, max: number) =>
z.string().min(min).max(max);
Применение:
const nickname = minMaxString(3, 20);
const title = minMaxString(5, 100);
Фабрики особенно полезны для:
Переиспользование часто строится через расширение существующих схем.
const baseUser = z.object({
id: z.string().uuid(),
createdAt: z.date(),
});
const fullUser = baseUser.extend({
email: z.string().email(),
});
const authFields = z.object({
email: z.string().email(),
password: z.string(),
});
const profileFields = z.object({
name: z.string(),
});
const user = authFields.merge(profileFields);
Разница:
Бизнес-правила часто повторяются и могут быть вынесены в функции.
const noSpaces = (value: string) => !value.includes(" ");
Использование:
export const username = z.string().refine(noSpaces, {
message: "Строка не должна содержать пробелы",
});
Более универсальный вариант:
export const createNoSpacesValidator = (message: string) =>
z.string().refine((val) => !val.includes(" "), { message });
Переиспользуемые трансформеры позволяют унифицировать входные данные.
export const toNumber = z.preprocess((val) => {
if (typeof val === "string") return Number(val);
return val;
}, z.number());
Использование:
const age = toNumber;
Подходит для:
Для структур с самоссылками применяется z.lazy:
const category: z.ZodType<any> = z.lazy(() =>
z.object({
name: z.string(),
children: z.array(category).optional(),
})
);
Такой подход позволяет:
В зрелых проектах создаётся слой доменных валидаторов.
Пример структуры:
validators/
primitives/
email.ts
uuid.ts
domain/
user.ts
product.ts
factories/
stringRange.ts
Пример доменного валидатора:
import { email, password } from "../primitives";
export const loginSchema = z.object({
email,
password,
});
Часто требуется извлекать части схем:
const user = z.object({
id: z.string(),
email: z.string(),
password: z.string(),
});
const publicUser = user.pick({
id: true,
email: true,
});
const safeUser = user.omit({
password: true,
});
const updateUser = user.partial();
Эти методы создают новые переиспользуемые формы одной модели.
Для различения идентификаторов используется branding:
const UserId = z.string().uuid().brand<"UserId">();
const OrderId = z.string().uuid().brand<"OrderId">();
Преимущества:
Часто создаются универсальные обёртки:
export const requiredString = z.string().min(1);
export const optionalString = z.string().optional();
export const positiveNumber = z.number().positive();
Эти утилиты формируют единый стиль валидации по всему проекту.
Переиспользуемые валидаторы часто включают стандартизированные сообщения:
export const email = z.string().email({
message: "Некорректный email",
});
Или через фабрики:
export const requiredField = (field: string) =>
z.string().min(1, `${field} является обязательным`);
Модульный подход позволяет тестировать валидаторы отдельно от бизнес-логики:
test("email validator", () => {
expect(email.safeParse("test@mail.com").success).toBe(true);
expect(email.safeParse("invalid").success).toBe(false);
});
Изоляция схем повышает стабильность всей системы валидации.
Систематическое применение переиспользуемых валидаторов приводит к: