Валидация с Zod

Валидация данных — ключевой элемент при работе с пользовательским вводом, сетевыми запросами и формами. В контексте SvelteKit она выполняет сразу несколько задач:

  • защита от некорректных данных на клиенте и сервере
  • обеспечение типобезопасности
  • унификация бизнес-логики
  • упрощение обработки ошибок

Использование библиотеки Zod позволяет описывать схемы данных декларативно и переиспользовать их как на клиенте, так и на сервере.


Основы Zod

Zod — это библиотека для декларативной валидации и парсинга данных с полной поддержкой TypeScript.

Установка

npm install zod

Простейшая схема

import { z } from 'zod';

const schema = z.string();

Валидация значения

schema.parse('hello'); // OK
schema.parse(123); // Ошибка

Метод parse выбрасывает исключение при невалидных данных.

Альтернатива — безопасная проверка:

const result = schema.safeParse(123);

if (!result.success) {
  console.log(result.error);
}

Интеграция Zod с SvelteKit

SvelteKit разделяет код на клиентский и серверный. Zod удобно использовать в:

  • +page.server.ts — обработка форм
  • +server.ts — API endpoints
  • компонентах Svelte — клиентская валидация

Валидация форм в SvelteKit

Определение схемы

import { z } from 'zod';

export const userSchema = z.object({
  email: z.string().email(),
  password: z.string().min(6),
});

Использование в серверном action

import { fail } from '@sveltejs/kit';
import { userSchema } from '$lib/schemas';

export const actions = {
  default: async ({ request }) => {
    const data = Object.fromEntries(await request.formData());

    const result = userSchema.safeParse(data);

    if (!result.success) {
      return fail(400, {
        errors: result.error.flatten().fieldErrors,
        values: data
      });
    }

    const validData = result.data;

    return {
      success: true
    };
  }
};

Обработка ошибок

Zod предоставляет удобный API для извлечения ошибок.

Формат ошибок

result.error.flatten()

Возвращает:

{
  fieldErrors: {
    email: ['Invalid email'],
    password: ['Too short']
  }
}

Использование в Svelte компоненте

<script>
  export let form;
</script>

<input name="email" />
{#if form?.errors?.email}
  <span>{form.errors.email[0]}</span>
{/if}

Продвинутые схемы

Вложенные объекты

const schema = z.object({
  user: z.object({
    name: z.string(),
    age: z.number()
  })
});

Массивы

z.array(z.string());

Опциональные поля

z.string().optional();

Значения по умолчанию

z.string().default('guest');

Кастомная валидация

refine

z.string().refine(val => val.includes('@'), {
  message: 'Must contain @'
});

superRefine

Позволяет проверять несколько полей одновременно:

z.object({
  password: z.string(),
  confirm: z.string()
}).superRefine((data, ctx) => {
  if (data.password !== data.confirm) {
    ctx.addIssue({
      path: ['confirm'],
      message: 'Passwords do not match',
      code: z.ZodIssueCode.custom
    });
  }
});

Трансформация данных

Zod может не только валидировать, но и преобразовывать данные.

z.string().transform(val => val.trim());

Пример: строка → число

z.string().transform(val => Number(val));

Использование с TypeScript

Одно из ключевых преимуществ Zod — автоматическое выведение типов.

const schema = z.object({
  name: z.string(),
  age: z.number()
});

type User = z.infer<typeof schema>;

Теперь User полностью соответствует схеме.


Повторное использование схем

Рекомендуется хранить схемы в отдельной директории:

src/lib/schemas/

Пример:

// user.ts
export const userSchema = z.object({
  email: z.string().email(),
  password: z.string().min(6)
});

Разделение схем для клиента и сервера

Иногда требуется разная строгость:

const baseSchema = z.object({
  email: z.string().email()
});

export const clientSchema = baseSchema;
export const serverSchema = baseSchema.extend({
  role: z.string()
});

Интеграция с UI библиотеками SvelteKit

Многие UI-библиотеки (например, формы) ожидают структуру ошибок. Zod хорошо подходит благодаря:

  • flatten() — удобный формат
  • issues — подробная информация
  • кастомные сообщения

Асинхронная валидация

Zod поддерживает async-валидацию:

z.string().refine(async (val) => {
  const exists = await checkUser(val);
  return !exists;
}, {
  message: 'User already exists'
});

Использование:

await schema.parseAsync(data);

Комбинирование схем

merge

const a = z.object({ a: z.string() });
const b = z.object({ b: z.number() });

const merged = a.merge(b);

extend

const extended = a.extend({
  c: z.boolean()
});

Дискриминированные объединения

Полезно для сложных форм:

const schema = z.discriminatedUnion('type', [
  z.object({ type: z.literal('a'), value: z.string() }),
  z.object({ type: z.literal('b'), value: z.number() })
]);

Валидация query и params

В SvelteKit:

export const load = ({ url }) => {
  const schema = z.object({
    page: z.string().transform(Number)
  });

  const result = schema.safeParse({
    page: url.searchParams.get('page')
  });

  if (!result.success) {
    return { page: 1 };
  }

  return result.data;
};

Обработка FormData

FormData всегда содержит строки, поэтому часто требуется преобразование:

const schema = z.object({
  age: z.string().transform(Number)
});

Или через preprocess:

z.preprocess(
  val => Number(val),
  z.number()
);

Централизация валидации

Рекомендуемый подход:

  • схемы — в lib/schemas

  • переиспользование в:

    • actions
    • endpoints
    • клиентах
  • единый формат ошибок


Производительность

Zod работает достаточно быстро, но при:

  • больших схемах
  • частых вызовах

рекомендуется:

  • избегать лишних трансформаций
  • кэшировать схемы
  • не создавать схемы внутри функций

Практический паттерн: универсальная функция валидации

export function validate(schema, data) {
  const result = schema.safeParse(data);

  if (!result.success) {
    return {
      success: false,
      errors: result.error.flatten().fieldErrors
    };
  }

  return {
    success: true,
    data: result.data
  };
}

Связка с формами и состоянием

Zod удобно использовать вместе с:

  • form actions
  • store (например, writable)
  • кастомными form-хелперами

Это позволяет построить предсказуемую архитектуру:

  • схема → валидация → ошибки → UI

Частые ошибки

1. Использование parse вместо safeParse Приводит к выбросу исключений.

2. Отсутствие трансформации FormData Все значения приходят строками.

3. Дублирование схем Нарушает DRY-принцип.

4. Смешивание клиентской и серверной логики Следует четко разделять зоны ответственности.


Расширение Zod

Можно создавать собственные утилиты:

const email = () => z.string().email();

const password = () => z.string().min(8);

Использование:

z.object({
  email: email(),
  password: password()
});

Итоговая архитектура

Грамотно выстроенная работа с Zod в SvelteKit выглядит так:

  • декларативные схемы
  • единый источник правды
  • повторное использование
  • строгая типизация
  • прозрачная обработка ошибок

Это делает код более предсказуемым, масштабируемым и безопасным.