Валидация на клиенте и сервере
Клиентская валидация улучшает 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 полям |
Подменяются так же легко | Проверяй все поля на сервере |