JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Валидация данных в Route Handlers:

Валидация данных в Route Handlers: zod, другие библиотеки

Модуль 4: Node.js, Next.js и Angular
12 уровень , 5 лекция
Открыта

1. Подходы к валидации: ручная проверка vs. библиотеки

Валидация (от англ. validation — "подтверждение правильности") — это процесс проверки входящих данных на соответствие определённым правилам: типам, диапазонам значений, обязательности полей и т.д.

Почему это важно:

  • Безопасность: если не проверять, что клиент прислал правильные данные, можно получить ошибки, уязвимости, или даже "сломать" базу данных.
  • Удобство: корректные ошибки для клиента, а не просто "500 Internal Server Error".
  • Предсказуемость: ваш backend всегда работает с ожидаемым форматом данных.
  • Меньше багов: меньше "магических" падений приложения из-за неожиданных undefined, null или "42" вместо email.

В Next.js Route Handlers валидация особенно актуальна, потому что:

  • Вы часто принимаете данные в формате JSON (например, из форм или fetch-запросов).
  • Ваш API может быть публичным (или почти публичным).
  • Вы хотите возвращать дружелюбные и понятные ошибки.

Вариант 1: Ручная проверка (oldschool)

Можно проверять каждое поле "вручную", например:


// Пример Route Handler (POST /api/users)
export async function POST(req) {
  const body = await req.json();
  if (typeof body.name !== 'string' || body.name.length < 2) {
    return new Response('Invalid name', { status: 400 });
  }
  if (typeof body.email !== 'string' || !body.email.includes('@')) {
    return new Response('Invalid email', { status: 400 });
  }
  // ...
  // Если всё ок — продолжаем обработку
}

Минусы:
- Много копипасты.
- Сложно поддерживать, если схема сложная.
- Легко ошибиться (например, забыть проверить какое-то поле).
- Никакой автоматической генерации ошибок.

Вариант 2: Использование библиотеки для валидации (best practice)

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

Плюсы:

  • Лаконично и наглядно.
  • Гибкая настройка (разные типы, вложенность, массивы, условия).
  • Автоматическая генерация подробных ошибок.
  • Возможность автогенерации типов TypeScript.
  • Легко тестировать и переиспользовать схемы.

Самые популярные библиотеки:

  • zod — фаворит современного фронтенда и Next.js.
  • yup — раньше был стандартом де-факто, сейчас уступает zod.
  • joi — очень мощная, популярна в Node.js.
  • superstruct — легковесная, похожа на zod.

В этой лекции мы подробно разберём zod (он идеально вписывается в Next.js), а также кратко коснёмся других вариантов.

2. Основы работы с zod

Установка

Для начала, установим zod (в проекте Next.js):

npm install zod
# или
yarn add zod

Простейший пример схемы

import { z } from "zod";

// Описываем схему данных пользователя
const userSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  age: z.number().int().min(0).max(120).optional(),
});
  • z.string() — строка.
  • .min(2) — минимум 2 символа.
  • .email() — валидный email.
  • .number().int().min(0).max(120).optional() — необязательное целое число от 0 до 120.

Проверка данных по схеме

const data = {
  name: "Иван",
  email: "ivan@example.com",
  age: 25,
};

const result = userSchema.safeParse(data);

if (!result.success) {
  console.log(result.error.issues);
  // Здесь массив ошибок, можно вернуть его клиенту
} else {
  console.log(result.data); // Гарантированно валидные данные
}
  • safeParse не выбрасывает исключение, а возвращает объект с success и error.
  • Если данные валидны — они лежат в result.data.
  • Если нет — в result.error.issues подробное описание ошибок.

3. Валидация данных в Route Handler: пошаговый пример

Шаг 1: Описываем схему

// /app/api/users/route.js
import { z } from "zod";

const userSchema = z.object({
  name: z.string().min(2, "Имя должно быть не короче 2 символов"),
  email: z.string().email("Неверный формат email"),
  age: z.number().int().min(0, "Возраст не может быть отрицательным").max(120).optional(),
});

Шаг 2: Используем схему для валидации данных

export async function POST(req) {
  let body;
  try {
    body = await req.json();
  } catch {
    return Response.json({ error: "Некорректный JSON" }, { status: 400 });
  }

  const result = userSchema.safeParse(body);

  if (!result.success) {
    // Собираем удобочитаемые сообщения об ошибках
    const errors = result.error.issues.map(issue => ({
      field: issue.path.join('.'),
      message: issue.message,
    }));
    return Response.json({ errors }, { status: 400 });
  }

  // Данные валидны!
  const user = result.data;
  // Здесь можно сохранять пользователя, отправлять ответ и т.д.
  return Response.json({ user }, { status: 201 });
}

Обратите внимание:
- Если JSON некорректный — сразу возвращаем ошибку.
- Если поля не проходят валидацию — возвращаем массив ошибок, чтобы фронтенд мог их красиво показать.
- Если всё ок — работаем с гарантированно валидными данными.

4. Валидация сложных структур: вложенные объекты, массивы

zod умеет валидировать не только "плоские" объекты, но и вложенные структуры, массивы, опциональные поля и т.д.

Массив пользователей

const usersSchema = z.array(userSchema);

const result = usersSchema.safeParse([
  { name: "Анна", email: "anna@example.com" },
  { name: "Боб", email: "bob@example.com", age: 200 }, // Ошибка: возраст
]);

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

const addressSchema = z.object({
  city: z.string(),
  zip: z.string().length(6),
});

const userWithAddressSchema = userSchema.extend({
  address: addressSchema.optional(),
});

5. Полезные нюансы

Генерация типов TypeScript из схемы

Одна из самых крутых фишек zod — генерация типов TS на лету:

import { z } from "zod";

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

// Тип User будет совпадать со схемой!
type User = z.infer<typeof userSchema>;

Теперь вы можете использовать тип User везде в коде, и если измените схему — тип обновится автоматически.

Сравнение zod, yup, joi, superstruct

Библиотека Синтаксис TS-интеграция Сообщества Особенности
zod Ясный, декларативный Отличная Очень активное Прямо для TypeScript, быстрый
yup Похож на joi Посредственная Среднее Близок по стилю к joi, но проще
joi Более громоздкий Нет Большое "Классика" Node.js, для больших схем
superstruct Минималистичный Хорошая Маленькое Очень легкий, чуть менее гибкий

Почему zod?
- Легко учить и читать.
- Прекрасно работает с TypeScript.
- Очень быстро развивается.
- Идеально подходит для Next.js и современных проектов.

Интеграция с Frontend: валидация на клиенте и сервере

zod можно использовать не только в Route Handlers, но и на клиенте — например, для проверки форм. Это позволяет использовать одну и ту же схему и на фронте, и на бэке (DRY-подход).

Пример:
- Вынесли схему в /lib/schemas/user.ts.
- Импортируем её и в Route Handler, и в компонент формы.
- Валидация будет одинаковой и предсказуемой.

6. Типичные ошибки при валидации данных в Route Handlers

Ошибка №1: Не проверяется тело запроса на валидный JSON.
Если клиент отправил битый JSON, await req.json() выбросит ошибку. Всегда оборачивайте в try/catch и возвращайте понятное сообщение.

Ошибка №2: Не возвращается подробная информация об ошибках.
Плохо: просто "400 Bad Request". Хорошо: массив ошибок с указанием поля и сообщения.

Ошибка №3: Слишком "жёсткая" схема.
Если вы не используете .optional() для необязательных полей, клиенту придётся всегда отправлять все поля — это неудобно.

Ошибка №4: Валидация только на фронте, но не на сервере.
Никогда не доверяйте данным с клиента! Даже если у вас суперкрутой React с Formik и yup — на сервере всё равно должна быть валидация.

Ошибка №5: Использование устаревших или плохо поддерживаемых библиотек.
Например, joi — отличная штука для старого Node.js, но для Next.js и TypeScript лучше выбрать zod.

Ошибка №6: Нарушение типов данных.
Если вы ожидаете число, а клиент прислал строку ("25" вместо 25), zod по умолчанию не преобразует типы (в отличие от yup). Используйте .transform() или .refine() для кастомных преобразований.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ