Общее использование одной схемы Zod между фронтендом на React и бэкендом на Node
Узнайте, как одна схема Zod может проверять формы React, ответы API, тела запросов Express и переменные окружения, одновременно генерируя соответствующие типы TypeScript.
Проверка данных необходима в любом приложении, но команды часто добавляют её постепенно — отдельная библиотека для фронтенда, другая — для бэкенда, причем одни и те же правила копируются в нескольких местах. Zod стал любимым инструментом среди разработчиков JavaScript и TypeScript именно потому, что избавляет от такого беспорядка: вы пишете одну схему, которая одновременно проверяет ваши данные и генерирует соответствующий тип TypeScript, готовый к использованию как в браузере, так и на сервере.
1. Что такое Zod?
Zod — это библиотека схем, созданная с учётом TypeScript с самого начала. Вы описываете структуру данных один раз, и Zod использует это описание для проверки значений во время выполнения и для автоматического формирования типа TypeScript — нет необходимости писать отдельный интерфейс, и не существует риска дисбаланса между ним и правилами проверки.
import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
type User = z.infer<typeof UserSchema>;
// { name: string; email: string; age?: number }
Одна такая схема одновременно покрывает три функции: она документирует структуру данных, обеспечивает её соблюдение во время выполнения и предоставляет статический тип, от которого зависят редактор и компилятор.
2. Почему Zod превосходит альтернативы
Основное преимущество — автоматическое определение типов. Библиотеки вроде Yup или Joi обычно требуют от вас поддерживать схему проверки наряду с вручную написанным интерфейсом TypeScript, полагаясь на то, что они не будут расходиться по мере изменения кодовой базы. Zod полностью устраняет этот риск: тип выводится непосредственно из схемы, поэтому нет необходимости синхронизировать что-либо ещё.
Zod также имеет небольшой размер и не требует внешних зависимостей, что делает его одинаково удобным как в пакетах фронтенда, ориентированных на минимизацию размера, так и в сервисах Node.js. Его цепочечная, комбинируемая API позволяет даже при сложной валидации — с вложенными объектами, объединениями и полями, зависящими друг от друга — сохранять код понятным и читаемым, вместо того чтобы превращаться в клубок специальных вспомогательных функций.
3. Использование Zod в приложении React
3.1 Валидация форм с помощью React Hook Form
Zod подключается непосредственно к React Hook Form через пакет @hookform/resolvers.
npm install zod react-hook-form @hookform/resolvers
// components/SignupForm.tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const SignupSchema = z.object({
name: z.string().min(2, 'Name is too short'),
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});
type SignupData = z.infer<typeof SignupSchema>;
export function SignupForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<SignupData>({
resolver: zodResolver(SignupSchema),
});
const onSubmit = (data: SignupData) => {
console.log('Valid data:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} placeholder="Name" />
{errors.name && <p>{errors.name.message}</p>}
<input {...register('email')} placeholder="Email" />
{errors.email && <p>{errors.email.message}</p>}
<input type="password" {...register('password')} placeholder="Password" />
{errors.password && <p>{errors.password.message}</p>}
<button type="submit">Sign Up</button>
</form>
);
}
Не требуется ручной отслеживание состояния ошибок и нет необходимости в дублирующихся объявлениях типов — одна схема одновременно обрабатывает проверку валидности, предоставляет сообщения об ошибках рядом с каждым полем и определяет тип объекта data, отправляемого в TypeScript.
3.2 Проверка ответов API
Zod также полезен при обработке входных данных приложения — например, для проверки того, соответствуют ли данные, возвращаемые API, ожидаемым значениям, поскольку нельзя полагаться только на типы на этапе компиляции для гарантии этого.
import { z } from 'zod';
const PostSchema = z.object({
id: z.number(),
title: z.string(),
body: z.string(),
});
const PostsResponseSchema = z.array(PostSchema);
async function fetchPosts() {
const res = await fetch('/api/posts');
const json = await res.json();
const result = PostsResponseSchema.safeParse(json);
if (!result.success) {
console.error(result.error.flatten());
throw new Error('Invalid API response shape');
}
return result.data; // fully typed Post[]
}
Этот подход позволяет обнаруживать некорректные или неожиданные ответы до того, как они приведут к скрытым сбоям в интерфейсе пользователя.
4. Использование Zod в фреймворке Node.js / Express
4.1 Проверка корпуса запросов
npm install zod express
// schemas/user-schema.ts
import { z } from 'zod';
export const CreateUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
export type CreateUserInput = z.infer<typeof CreateUserSchema>;
// middleware/validate.ts
import { Request, Response, NextFunction } from 'express';
import { ZodSchema } from 'zod';
export function validate(schema: ZodSchema) {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.flatten() });
}
req.body = result.data;
next();
};
}
// routes/users.ts
import { Router } from 'express';
import { validate } from '../middleware/validate';
import { CreateUserSchema } from '../schemas/user-schema';
const router = Router();
router.post('/users', validate(CreateUserSchema), (req, res) => {
// req.body is now guaranteed to match CreateUserInput
const { name, email, age } = req.body;
res.status(201).json({ name, email, age });
});
export default router;
Такая настройка обеспечивает каждому маршруту единый, декларативный шаг проверки с централизованной обработкой ошибок вместо их повторного использования в виде встроенных проверок с помощью оператора if в каждом обработчике.
4.2 Проверка переменных окружения
Одним из недооценённых, но мощных способов использования Zod является проверка значения process.env при запуске приложения, что позволяет избежать проблем из-за некорректной конфигурации и предотвратить появление запутанных ошибок позже.
// config/env.ts
import { z } from 'zod';
const EnvSchema = z.object({
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
NODE_ENV: z.enum(['development', 'production', 'test']),
});
export const env = EnvSchema.parse(process.env);
Если отсутствует какая-либо необходимая переменная или она имеет неверный формат, процесс сразу же завершается с понятным сообщением об ошибке — это гораздо проще для диагностики, чем загадочная ошибка, скрытая внутри запроса к базе данных.
5. Настоящий преимущество: единая схема, используемая во всей стек-архитектуре
Поскольку схемы Zod представляют собой просто значения TypeScript, ничто не мешает разместить их в общем пакете или общей папке внутри монорепозитория и использовать абсолютно одинаковую схему как на клиенте, так и на сервере.
/packages
/shared
/schemas
user-schema.ts <-- used by both React app and Express API
/web (React/Next.js)
/api (Node/Express)
// packages/shared/schemas/user-schema.ts
import { z } from 'zod';
export const CreateUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
export type CreateUserInput = z.infer<typeof CreateUserSchema>;
React-приложение использует эту схему для проверки формы регистрации перед её отправкой. API Express также опирается на ту же схему для верификации поступающих данных. Когда схема меняется — например, появляется новое обязательное поле — оба слоя синхронно реагируют на изменения, и TypeScript немедленно выявляет код, который ещё не адаптирован к новой структуре. Это предотвращает возникновение целого класса ошибок, когда проверки с клиентской и серверной сторон со временем начинают отклоняться друг от друга.
6. Лучшие практики
- Используйте
safeParse, когда неудача является нормальным и ожидаемым результатом (данные из формы, ответы от сторонних API), аparse, который вызывает исключение, оставьте для случаев, когда данные действительно никогда не могут быть недопустимыми, например, переменные окружения, проверяемые при запуске.
.transform() для очистки данных в процессе самой верификации — удаления пробелов, принудительной конвертации типов — вместо отдельной процедуры нормализации позже.z.infer для всего, что уже имеет схему, чтобы ваши типы и логика верификации никогда не теряли синхронизации.error.flatten() или error.format(), что позволяет коду фронтенда легко соотносить каждую ошибку с соответствующим полем формы.7. Заключение
Zod — это не просто типичная библиотека валидации: она полностью меняет подход к связи между валидацией и определением типов. Генерируя типы TypeScript непосредственно из схем во время выполнения, она без проблем устраняет проблему расхождений между определениями типов и правилами валидации. К этому добавляются её минимальные требования к ресурсам, модульная архитектура и одинаковое поведение как в браузере, так и в Node — всё это делает Zod идеальным выбором для полноценных проектов на TypeScript, построенных с использованием React и Node.js.
Следующие шаги:
- Изучите
zod-to-openapi, если вам нужно генерировать документацию OpenAPI непосредственно из ваших схем - Ознакомьтесь с методами
.refine()и.superRefine()для создания пользовательской логики валидации, охватывающей несколько полей - Посмотрите tRPC, который использует схемы Zod для обеспечения полной типобезопасности на всём протяжении API
Связанные статьи
- Zod против express-validator: два подхода к валидации в Express — Сравнение валидации запросов с использованием схемы через Zod и посредников express-validator на основе цепочек, включая настройку, форматирование ошибок и распространённые проблемы.
- Предложения TC39 в 2026 году: декораторы, Temporal и Signals объяснены — Практический обзор трёх предложений TC39 — встроенных декораторов, API Temporal и Signals — и их значение для разработчиков JavaScript и TypeScript полноценных веб-приложений.