Валидация на клиенте и сервере

Клиентская валидация улучшает UX (быстрая обратная связь), серверная валидация обеспечивает безопасность; оба рубежа обязательны и дополняют друг друга.

Зачем нужно

Клиентская валидация — это удобство: пользователь видит ошибку немедленно, без запроса к серверу. Но она не является защитой: любой запрос можно отправить в обход браузера (DevTools, curl, Postman). Серверная валидация — обязательный рубеж безопасности: всё, что приходит от клиента, считается ненадёжным. Без неё возможны SQL-инъекции, XSS, переполнение базы данных.

Где используется

  • Клиентская: формы регистрации, входа, оплаты, обратной связи
  • Серверная: REST API, GraphQL, серверные формы (PHP, Node.js, Python)
  • В SPA — обе одновременно (React Hook Form + Zod на фронте и бэке)

Клиентская валидация — уровни

1. HTML5 встроенная валидация

<form novalidate>  <!-- novalidate отключает нативный UI, но не атрибуты -->
  <label for="email">Email</label>
  <input type="email" id="email" name="email"
         required
         minlength="5"
         maxlength="254"
         pattern="[^@\s]+@[^@\s]+\.[^@\s]+">

  <label for="age">Возраст</label>
  <input type="number" id="age" name="age" min="18" max="120">

  <label for="website">Сайт</label>
  <input type="url" id="website" name="website">

  <button type="submit">Отправить</button>
</form>

Атрибуты HTML5-валидации: required, minlength, maxlength, min, max, pattern, type.

2. Constraint Validation API

const form = document.querySelector('form');
const emailInput = document.getElementById('email');

// Проверить поле программно
console.log(emailInput.validity.valid);     // true/false
console.log(emailInput.validity.valueMissing); // нет required значения
console.log(emailInput.validity.typeMismatch); // неверный формат
console.log(emailInput.validationMessage);  // текст ошибки браузера

// Установить кастомное сообщение об ошибке
emailInput.setCustomValidity('Введите рабочий email');
emailInput.setCustomValidity(''); // сбросить ошибку

form.addEventListener('submit', (e) => {
  if (!form.checkValidity) {
    e.preventDefault();
    // показать ошибки
  }
});

3. Кастомная JavaScript-валидация

function validateEmail(value) {
  const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return re.test(value);
}

function validatePassword(value) {
  return {
    length: value.length >= 8,
    uppercase: /[A-Z]/.test(value),
    digit: /\d/.test(value),
  };
}

Серверная валидация

// Express.js + Zod
import { z } from 'zod';

const schema = z.object({
  email: z.string.email,
  password: z.string.min(8),
  age: z.number.int.min(18),
});

app.post('/register', (req, res) => {
  const result = schema.safeParse(req.body);

  if (!result.success) {
    return res.status(422).json({
      errors: result.error.flatten.fieldErrors
    });
  }

  // Данные валидны — продолжаем
  const { email, password, age } = result.data;
  // ...
});

Правило двух рубежей

Пользователь → [Клиентская валидация] → [Серверная валидация] → База данных
                  ↓ UX и скорость          ↓ Безопасность
  • Клиент: быстрая обратная связь, экономия запросов
  • Сервер: единственный надёжный рубеж безопасности

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

Ошибка Почему плохо Как правильно
Только клиентская валидация Обходится прямым запросом Всегда дублировать на сервере
Серверная ошибка без сообщения пользователю UX страдает Возвращай структурированные ошибки по полям
novalidate без замены кастомной валидацией Форма не проверяется вообще Используй Constraint API или библиотеку
Доверие к hidden полям Подменяются так же легко Проверяй все поля на сервере

Связанные темы

Ресурсы