Данные, возвращаемые из базы данных, не обладают гарантиями
корректности на уровне приложения. Даже при строгой схеме таблиц типы в
SQL и типы в JavaScript не совпадают напрямую: строки могут приходить
вместо чисел, NULL вместо объектов, даты — в виде строк, а
вложенные структуры часто требуют ручной нормализации. В таких условиях
необходим слой валидации, который приводит «сырые» данные к
предсказуемой форме и одновременно отсеивает некорректные записи.
Библиотека Zod решает задачу проверки и преобразования данных на уровне выполнения программы, формируя строгие схемы, которые одновременно описывают структуру данных и выполняют их валидацию.
Zod строится вокруг идеи схемы как исполняемого описания данных. Схема не только описывает тип, но и:
При работе с базой данных это позволяет рассматривать каждую строку результата как потенциально небезопасный объект, требующий проверки перед использованием в бизнес-логике.
Рассмотрим типичный случай: таблица пользователей.
type RawUserRow = {
id: unknown;
email: unknown;
age: unknown;
created_at: unknown;
};
Данные приходят из SQL-драйвера без строгих типов. С помощью Zod определяется схема:
import { z } from "zod";
const UserSchema = z.object({
id: z.number(),
email: z.string().email(),
age: z.number().int().positive(),
created_at: z.string().datetime()
});
Применение:
const parsedUser = UserSchema.parse(rowFromDb);
При несоответствии структуры выбрасывается исключение, содержащее описание ошибок по каждому полю.
База данных часто возвращает значения в формате, отличном от ожидаемого:
INTEGER → иногда приходит как string;BOOLEAN → может быть 0 | 1;TIMESTAMP → строка ISO или UNIX time;NULL → неопределённые значения.Zod позволяет учитывать такие несоответствия через преобразования.
const ProductSchema = z.object({
id: z.coerce.number(),
price: z.coerce.number(),
in_stock: z.coerce.boolean()
});
z.coerce автоматически приводит входные данные:
"123" → 123"1" → true"0" → falseЭто особенно полезно при работе с драйверами, возвращающими строки.
SQL допускает NULL, что требует явного отражения в
схеме.
const CommentSchema = z.object({
id: z.number(),
text: z.string(),
edited_at: z.string().datetime().nullable()
});
Разница между nullable() и optional():
nullable() — значение может быть nulloptional() — значение может отсутствоватьnullable().optional() — отсутствует или
nullconst Schema = z.object({
nickname: z.string().nullable().optional()
});
Zod позволяет не только проверять, но и нормализовать данные.
const OrderSchema = z.object({
id: z.number(),
total: z.string().transform((val) => Number(val)),
created_at: z.string().transform((val) => new Date(val))
});
Результат:
"99.50"99.5 (number)Такая трансформация полезна для унификации слоя данных перед бизнес-логикой.
База данных часто возвращает списки записей. Для этого используется
z.array.
const UserListSchema = z.array(UserSchema);
Применение:
const users = UserListSchema.parse(rowsFromDb);
Если хотя бы один элемент массива не соответствует схеме, валидация завершится ошибкой.
При выборке через SELECT часто возвращается неполный
набор полей. В таких случаях используется partial.
const UserPreviewSchema = UserSchema.partial();
Теперь все поля становятся необязательными:
SEL ECT id, emailТакже возможно комбинирование:
const Schema = UserSchema.pick({
id: true,
email: true
});
При нормализации данных из нескольких таблиц используется композиция:
const ProfileSchema = z.object({
bio: z.string(),
avatar_url: z.string().url()
});
const UserWithProfileSchema = z.object({
id: z.number(),
email: z.string(),
profile: ProfileSchema
});
Такая структура отражает join-операции SQL:
SELECT users.id, users.email, profile.bio, profile.avatar_url
FR OM users
JOIN profile ON profile.user_id = users.id;
После преобразования данные группируются в вложенный объект и проходят валидацию целиком.
При работе с внешними источниками или legacy-таблицами тип
unknown становится стандартом входа.
const SafeUserSchema = z.object({
id: z.unknown().transform((v) => Number(v)),
email: z.string().email(),
is_active: z.unknown().transform((v) => v === 1 || v === true)
});
Такой подход гарантирует, что любые входные данные приводятся к ожидаемому виду через явные правила.
Zod возвращает структурированные ошибки:
const result = UserSchema.safeParse(data);
Форма результата:
success: true → данные валидныsuccess: false → содержит errorОшибки имеют детализированную структуру:
path)Пример обработки:
if (!result.success) {
const issues = result.error.issues;
}
Это позволяет строить диагностические механизмы для анализа данных из базы.
Zod часто используется поверх ORM или query builder’ов.
const UserSchema = z.object({
id: z.number(),
email: z.string()
});
const user = UserSchema.parse(prismaUser);
const rows = await db.query("SEL ECT * FR OM users");
const users = z.array(UserSchema).parse(rows);
Такой слой обеспечивает защиту от неконсистентных данных, даже если схема БД изменена.
Иногда таблица содержит разные типы записей:
const EventSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("click"),
x: z.number(),
y: z.number()
}),
z.object({
type: z.literal("scroll"),
scrollTop: z.number()
})
]);
Это особенно полезно для event-логов, audit-таблиц и очередей задач.
Базы данных часто возвращают даты в виде строк:
const LogSchema = z.object({
created_at: z.string().transform((v) => new Date(v))
});
Дополнительно можно стандартизировать формат:
const Schema = z.object({
created_at: z.coerce.date()
});
После валидации приложение работает только с объектами
Date, исключая неоднозначность форматов.
При миграциях и устаревших таблицах встречаются неполные записи. В таких случаях используется комбинация:
const LegacyUserSchema = z.object({
id: z.coerce.number(),
email: z.string().email().optional(),
age: z.coerce.number().nullable()
});
Такой подход позволяет обрабатывать реальные данные без их предварительной очистки в БД.
При проектировании схем Zod для БД обычно выделяются уровни:
Пример разделения:
const RawUserSchema = z.object({
id: z.unknown(),
email: z.unknown(),
created_at: z.unknown()
});
const DomainUserSchema = z.object({
id: z.number(),
email: z.string(),
createdAt: z.date()
});
Основная идея использования Zod с базой данных заключается в том, что проверка выполняется:
Это формирует строгую границу между: