Zod представляет декларативный способ описания структур данных, где схема одновременно выступает валидатором и источником типов TypeScript. Такой подход позволяет использовать единое определение данных как на этапе выполнения, так и на этапе компиляции.
Базовая идея заключается в том, что каждая схема описывает форму объекта, примитив или композицию, а затем может быть использована для вывода типов и валидации входящих данных.
import { z } from "zod";
const UserSchema = z.object({
id: z.string(),
name: z.string(),
age: z.number().int().positive(),
});
Схемы Zod становятся фундаментом для последующей генерации OpenAPI, так как они уже содержат полное описание структуры данных.
В Zod поддерживаются примитивные типы, а также их композиции. На
уровне OpenAPI это соответствует схемам компонентов
(components/schemas), которые могут быть
переиспользованы.
Основные примитивы:
z.string();
z.number();
z.boolean();
z.null();
z.undefined();
Композиционные конструкции:
z.array(schema) — массивz.object({...}) — объектz.union([a, b]) — объединение типовz.intersection(a, b) — пересечениеz.optional() — необязательностьz.nullable() — допускается nullПример композиции:
const ResponseSchema = z.object({
data: z.union([
z.string(),
z.array(z.string())
]),
error: z.nullable(z.string()),
});
При генерации OpenAPI такие конструкции трансформируются в
oneOf, anyOf, nullable и массивы
схем.
Для преобразования схем используется экосистема вроде zod-to-openapi, которая расширяет Zod возможностью аннотирования схем метаданными OpenAPI.
Подключение расширения:
import { extendZodWithOpenApi } from "zod-to-openapi";
import { z } from "zod";
extendZodWithOpenApi(z);
После расширения каждая схема может быть описана с дополнительной информацией:
const UserSchema = z.object({
id: z.string().openapi({ example: "123" }),
name: z.string().openapi({ example: "Alex" }),
}).openapi("User");
Метод .openapi() задаёт имя компонента, которое затем
используется в спецификации OpenAPI как ссылка $ref.
Генерация OpenAPI требует централизованного реестра схем. Он
используется для формирования секции
components/schemas.
import { OpenAPIRegistry } from "zod-to-openapi";
const registry = new OpenAPIRegistry();
registry.register("User", UserSchema);
Каждая зарегистрированная схема становится доступной для маршрутов и может быть переиспользована в разных частях API.
Регистрация позволяет:
$refOpenAPI спецификация строится вокруг HTTP-операций. Для их описания используется регистрация путей:
registry.registerPath({
method: "get",
path: "/users/{id}",
responses: {
200: {
description: "User found",
content: {
"application/json": {
schema: UserSchema,
},
},
},
},
});
Каждый маршрут содержит:
Схемы Zod используются для всех этих элементов.
Параметры пути и query-string также описываются через Zod:
const ParamsSchema = z.object({
id: z.string(),
});
registry.registerPath({
method: "get",
path: "/users/{id}",
request: {
params: ParamsSchema,
},
responses: {
200: {
description: "OK",
content: {
"application/json": {
schema: UserSchema,
},
},
},
},
});
Преобразование:
z.string() → type: stringz.number() → type: numberz.object() → type: objectQuery-параметры автоматически отражаются как
in: query.
После регистрации всех схем и маршрутов формируется финальная OpenAPI спецификация.
import { OpenApiGeneratorV3 } from "zod-to-openapi";
const generator = new OpenApiGeneratorV3(registry.definitions);
const document = generator.generateDocument({
openapi: "3.0.0",
info: {
title: "API",
version: "1.0.0",
},
});
Результатом является JSON-документ, соответствующий OpenAPI 3.x, включающий:
pathscomponents/schemasparametersresponsesZod поддерживает дискриминируемые объединения, которые напрямую
транслируются в OpenAPI oneOf с
discriminator.
const ShapeSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("circle"),
radius: z.number(),
}),
z.object({
type: z.literal("square"),
size: z.number(),
}),
]);
В OpenAPI это превращается в:
oneOfdiscriminator.propertyName = "type"Также активно используются ссылки:
const A = z.object({
b: z.lazy(() => BSchema),
});
Ленивые ссылки позволяют описывать циклические зависимости между схемами, что важно для сложных доменных моделей.
При генерации OpenAPI на основе Zod-схем критично управление версионированием:
components/schemas выступает единым источником
правдыСтруктура обычно организуется через доменные модули:
UserSchemaAuthSchemaPaymentSchemaКаждая сущность становится независимым компонентом спецификации.
При масштабировании API на основе Zod и OpenAPI важны следующие подходы:
.openapi(name)oneOfСложные структуры данных (например, вложенные DTO) описываются через композицию Zod-схем, что позволяет сохранять согласованность между runtime-валидацией и контрактом API.
Особое значение имеет строгая типизация на уровне схем, так как она напрямую определяет корректность OpenAPI-документа и последующую генерацию клиентских SDK.