Обновлено: 5 августа 2026. Материал редакции Зерокодера.
Валидация кода на стороне сервера — обязательная проверка: клиентская валидация в браузере даёт удобство, но её обходят за минуту через DevTools или curl. Правило разработчика простое: всё, что проверили в форме, проверяем ещё раз на сервере, до записи в базу. Ниже — рабочий код обеих проверок.
Коротко, что нужно знать:
- Клиентская проверка отвечает за удобство, серверная — за безопасность. Одна другую не заменяет.
- Браузер умеет проверять поля сам: атрибуты
required,type,minlength,maxlength,min,max,patternи Constraint Validation API из документации MDN. - На сервере проверка идёт в порядке «тип → длина → формат». Если начать с формата, код падает на первом же запросе, где вместо строки прислали объект.
- Валидация не отменяет параметризованные запросы к базе и экранирование при выводе: OWASP прямо пишет, что как основная защита от инъекций она не годится.
- Регулярка для email из типовых подборок отбраковывает живые адреса — мы это замерили, таблица ниже.
Дальше — что такое валидация полей, как её обходят, код клиентской и серверной проверки и чек-лист, по которому её принимают на ревью.
Что такое валидация полей и валидация на вход
Валидация — это проверка данных на соответствие заранее описанным правилам. Валидация полей формы отвечает на вопрос «можно ли это принять»: заполнено ли поле, тот ли тип, та ли длина, тот ли формат. Валидацией на вход называют ту же проверку в момент, когда данные пересекают границу вашей программы, — приходят из формы, из запроса к API, из загруженного файла.
Корректность данных — не абстракция, а три конкретных вещи, которые ломаются без проверки:
- Целостность. В базу уезжает пустое имя, дата рождения «31.02.2001», цена «-500». Дальше это разбирают руками.
- Безопасность. Некорректный ввод — вход для SQL-инъекций и XSS. Валидация сужает, что вообще может попасть внутрь, хотя и не закрывает эти атаки целиком (об этом ниже). Про поиск таких мест в готовом коде у нас есть отдельный разбор: как искать уязвимости в программном коде.
- Опыт пользователя. Человек узнаёт об ошибке рядом с полем и сразу, а не после перезагрузки страницы с потерянными данными.
Проверка выполняется в двух местах. Валидация на стороне клиента идёт в браузере, до отправки: мгновенная реакция, ноль запросов к серверу. Валидация на стороне сервера идёт после получения данных: медленнее, зато её нельзя выключить из браузера и только на ней доступны правила, которые требуют данных с сервера, — уникальность email, остаток на складе, действующий промокод.
Разделение простое: браузер помогает человеку не ошибиться, сервер защищает систему от того, кто ошибается намеренно.

- ПОКАЖЕМ, КАК РАЗВЕРНУТЬ МОДЕЛЬ нейросети DEEPSEEK R1 ПРЯМО НА СВОЁМ КОМПЬЮТЕРЕ
- Где и как применять? Потестируем модель после установки на разных задачах
- Как дообучить модель под себя?
Почему валидация на стороне клиента не защищает

Документация MDN формулирует это без смягчений: «Никогда не доверяйте данным, передаваемым на сервер клиентской программой. Даже если ваша форма правильно валидируется и не допустит введение потенциально вредоносных данных на стороне клиента, злоумышленники по-прежнему могут изменить сетевой запрос».
Обойти проверку в браузере можно тремя способами, и ни один не требует квалификации:
- Открыть DevTools и удалить атрибут
requiredили добавить формеnovalidate— браузер перестанет проверять поле. - Отключить JavaScript. Вся логика на
addEventListener('submit', ...)просто не выполнится. - Не открывать страницу вообще. Запрос к вашему обработчику отправляется одной строкой из терминала.
curl -X POST https://example.com/register \
-H "Content-Type: application/json" \
-d '{"name":"","email":"не-email","password":"1"}'
Формы здесь нет, браузера нет, проверять нечего. Обработчик получит ровно то, что прислали. Поэтому OWASP в Input Validation Cheat Sheet пишет: «Input validation must be implemented on the server-side before any data is processed by an application’s functions, as any JavaScript-based input validation performed on the client-side can be circumvented by an attacker who disables JavaScript or uses a web proxy». И тут же называет рабочую схему: «Implementing both client-side JavaScript-based validation for UX and server-side validation for security is the recommended approach».
Вывод для разработчика: клиентская валидация — часть интерфейса, серверная — часть системы безопасности. Убрать первую значит испортить форму, убрать вторую значит остаться без проверки вовсе.
Валидация формы на стороне клиента: HTML-атрибуты
Половину работы браузер делает без единой строки JavaScript. Справочник MDN «Валидация форм на стороне клиента» перечисляет атрибуты валидации так: required — «Определяет, что для отправки формы данное поле предварительно должно быть заполнено»; minlength и maxlength — «Задаёт минимальную и максимальную длину текстовых данных (строк)»; min и max — для числовых полей; type — тип данных поля; pattern — «С помощью регулярного выражения, определяет шаблон, которому должны соответствовать вводимые данные».
Форма регистрации с этими атрибутами и с местами под сообщения об ошибках:
<form id="registrationForm" novalidate>
<label for="name">Имя</label>
<input type="text" id="name" name="name"
required minlength="2" maxlength="60" autocomplete="name">
<p id="name-error" class="field-error" aria-live="polite"></p>
<label for="email">Email</label>
<input type="email" id="email" name="email"
required maxlength="254" autocomplete="email">
<p id="email-error" class="field-error" aria-live="polite"></p>
<label for="password">Пароль</label>
<input type="password" id="password" name="password"
required minlength="8" maxlength="128" autocomplete="new-password">
<p id="password-error" class="field-error" aria-live="polite"></p>
<button type="submit">Зарегистрироваться</button>
</form>
Атрибут novalidate здесь стоит намеренно. MDN: он «отключает автоматическую валидацию браузером; это позволяет нашему скрипту взять управление валидацией на себя. Однако, это не отменяет поддержку Constraint Validation API или псевдоклассов, таких как :valid». То есть браузер продолжает считать состояние каждого поля, но не показывает свои всплывающие подсказки — их место занимают наши сообщения рядом с полем.
Атрибуты дают базовую валидацию полей бесплатно и работают даже с выключенным JavaScript. Дальше подключаем скрипт, чтобы управлять текстом ошибок.
Валидация на JavaScript: Constraint Validation API вместо alert
Типовой пример из старых руководств собирает значения через getElementById().value, гоняет их по регуляркам и показывает alert. Так делать не надо: модальное окно перекрывает форму, не говорит, какое из полей виновато, и недоступно для скринридера.
В браузере уже есть готовый механизм. По MDN: checkValidity() «Возвращает true, если значение элемента проходит валидацию, иначе возвращает false»; validity «Возвращает объект ValidityState, который содержит несколько свойств, описывающих состояние валидности элемента» — среди них valueMissing, typeMismatch, tooShort, tooLong, patternMismatch, rangeOverflow, rangeUnderflow. Свойство willValidate «Возвращает true, если элемент будет участвовать в валидации при отправке формы» — по нему удобно пропускать кнопки. Метод setCustomValidity(message) подставляет своё сообщение, а reportValidity() делает то же, что checkValidity(), но ещё и показывает проблему пользователю.
const form = document.getElementById('registrationForm');
const MESSAGES = {
valueMissing: 'Заполните поле',
typeMismatch: 'Проверьте формат адреса: нужно вида ivan@example.ru',
tooShort: 'Слишком коротко',
tooLong: 'Слишком длинно',
patternMismatch: 'Значение не подходит под формат',
};
function messageFor(field) {
const v = field.validity;
for (const key of Object.keys(MESSAGES)) {
if (v[key]) return MESSAGES[key];
}
return field.validationMessage;
}
function showError(field) {
const valid = field.checkValidity();
const box = document.getElementById(field.id + '-error');
if (!box) return valid; // поле без контейнера ошибки не роняет обработчик
if (valid) {
box.textContent = '';
field.removeAttribute('aria-invalid');
} else {
box.textContent = messageFor(field);
field.setAttribute('aria-invalid', 'true');
}
return valid;
}
// Только поля ввода. В form.elements лежат ещё и кнопки — см. пояснение ниже
const fields = form.querySelectorAll('input, select, textarea');
for (const field of fields) {
if (!field.willValidate) continue;
field.addEventListener('blur', () => showError(field));
field.addEventListener('input', () => {
if (field.getAttribute('aria-invalid')) showError(field);
});
}
form.addEventListener('submit', (event) => {
let firstBad = null;
for (const field of fields) {
if (!field.willValidate) continue;
if (!showError(field) && !firstBad) firstBad = field;
}
if (firstBad) {
event.preventDefault();
firstBad.focus();
}
});
Ошибка появляется при уходе из поля, пропадает по мере исправления, отправка блокируется, фокус едет к первому проблемному полю. Правила при этом описаны один раз — в HTML.
Две строки в этом коде выглядят перестраховкой, но именно из-за них он работает. Первая — список полей через querySelectorAll('input, select, textarea'), а не через form.elements: в коллекцию формы попадают и кнопки, а у <button type="submit"> свойство willValidate равно true, поэтому проверку по нему кнопка проходит. Дальше скрипт ищет контейнер '' + '-error', получает null и падает с TypeError — то есть до event.preventDefault() дело не доходит и форма уходит на сервер с невалидными данными. Мы поймали это на собственном примере при прогоне статьи в jsdom, а не в теории.
Вторая — проверка if (!box) return valid;: если у поля забыли добавить блок под сообщение, форма продолжает считать его валидность и не пропускает отправку, хотя текст ошибки показать негде. Сам willValidate при этом нужен — он отсекает скрытые, отключённые и readonly поля, которые проверять не надо.
Валидация кода на стороне сервера: рабочий пример

На сервере проверка идёт в другом порядке, чем в браузере. В браузере значение поля всегда строка — иначе его туда не ввести. В запросе строки может не быть вовсе. README пакета body-parser, на котором стоит разбор тела запроса в Express, предупреждает прямо: «As req.body‘s shape is based on user-controlled input, all properties and values in this object are untrusted and should be validated before trusting. For example, req.body.foo.toString() may fail in multiple ways, for example the foo property may not be there or may not be a string».
Отсюда порядок: сначала тип, потом длина, потом формат. Функция ниже не зависит ни от одной библиотеки и запускается как есть:
const EMAIL_RE =
/^[\w.!#$%&'*+/=?^`{|}~-]+@[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?(?:\.[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?)+$/i;
const RULES = {
name: { min: 2, max: 60 },
email: { min: 6, max: 254 },
password: { min: 8, max: 128 },
};
function validateRegistration(body) {
const errors = {};
const clean = {};
if (typeof body !== 'object' || body === null) {
return { ok: false, errors: { _form: 'Ожидался объект' }, data: null };
}
for (const [field, rule] of Object.entries(RULES)) {
const raw = body[field];
if (typeof raw !== 'string') { // объекты, массивы, числа, undefined
errors[field] = 'Поле должно быть строкой';
continue;
}
const value = raw.normalize('NFC').trim();
if (value.length < rule.min) {
errors[field] = `Минимум ${rule.min} символов`;
continue;
}
if (value.length > rule.max) {
errors[field] = `Максимум ${rule.max} символов`;
continue;
}
clean[field] = value;
}
if (!errors.email && !EMAIL_RE.test(clean.email)) {
errors.email = 'Неверный формат адреса';
}
const ok = Object.keys(errors).length === 0;
return { ok, errors, data: ok ? clean : null };
}
Функция возвращает три вещи: вердикт, словарь ошибок по полям и очищенные данные. Дальше в приложение уходит только data — исходный объект запроса не используется нигде.
Что пропускает типовая серверная валидация: наш замер
Мы взяли проверку, которая кочует по руководствам почти дословно — if (!name || !email || !password) плюс регулярка /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/ — и прогнали через неё четыре запроса в Node.js 5 августа 2026. Вот что она ответила:
| Что прислали в теле запроса | Вердикт проверки |
|---|---|
{"name": {}, "email": "ivan@zerocoder.ru", "password": []} |
OK, регистрируем |
{"name": ["a"], "email": "ivan@zerocoder.ru", "password": {}} |
OK, регистрируем |
| пароль из одного символа | OK, регистрируем |
| имя длиной 100 000 символов | OK, регистрируем |
Пустой объект и пустой массив в JavaScript истинны, поэтому !name их не ловит. Ни длина, ни тип не проверяются вообще. А обращение к строковому методу у такого значения роняет обработчик: body.email.trim() в нашем прогоне вернул TypeError: body.email.trim is not a function.
Вторая часть замера — сама регулярка. Мы сравнили её с алгоритмом, который браузер применяет к <input type="email"> (MDN приводит эквивалентное регулярное выражение), на семи адресах:
| Адрес | Регулярка из подборок | Алгоритм из MDN |
|---|---|---|
| ivan@zerocoder.ru | принят | принят |
| ivan.petrov+work@gmail.com | отклонён | принят |
| hello@zerocoder.online | отклонён | принят |
| info@school.technology | отклонён | принят |
| a@b.museum | отклонён | принят |
| user@localhost | отклонён | принят |
| ivan@почта.рф | отклонён | отклонён |
Хвост [a-zA-Z]{2,4} режет любую доменную зону длиннее четырёх букв и знак + в адресе. Проверка из спецификации мягче, но у неё своя дыра: группа домена в ней необязательная, поэтому user@localhost без точки проходит. Кириллический адрес не проходит ни там, ни там — MDN отдельно отмечает известные проблемы спецификации с международными доменными именами, так что поведение конкретного браузера с такими адресами стоит проверять руками.
Вывод: регулярку нельзя брать из чужой статьи не глядя. В нашем серверном примере выше хвост домена сделан обязательным (+ вместо *), поэтому user@localhost отклоняется, а info@school.technology проходит.
Как подключить валидацию к обработчику на сервере
Функцию проверки нужно поставить перед любой работой с данными. В Express для этого сначала подключают разбор тела запроса: в списке изменений версии 4.16.0 от 28 сентября 2017 года указано «Add express.json and express.urlencoded to parse bodies» — с этой версии отдельный пакет body-parser ставить не нужно. Ограничение размера тела по умолчанию — 100 кБ, и в документации body-parser советуют «не задавать очень высокий лимит и по возможности использовать значение по умолчанию».
const express = require('express');
const app = express();
app.use(express.json({ limit: '100kb' }));
app.use(express.urlencoded({ extended: false, limit: '100kb' }));
app.post('/register', (req, res) => {
const result = validateRegistration(req.body);
if (!result.ok) {
return res.status(400).json({ errors: result.errors });
}
const { name, email } = result.data;
// Параметризованный запрос: значения уходят отдельно от текста SQL
// await db.query('INSERT INTO users(name, email) VALUES ($1, $2)', [name, email]);
return res.status(201).json({ registered: email, name });
});
app.listen(3000);
Три детали, из-за которых обычно приходится переделывать:
- Код ответа. На отбракованные данные отдавайте
400, а не200с текстом ошибки: фронтенд и мониторинг разбирают именно статус. - Формат ошибок. Словарь «поле → сообщение» позволяет подсветить нужные поля разом. Одна строка «Ошибка валидации» этого не даёт.
- Правила, которых нет в браузере. Уникальность email, действующий промокод, остаток на складе — только на сервере и только после того, как формат уже проверен.
Если проверок много и они повторяются между обработчиками, их выносят в схему — в экосистеме Node это Zod, Joi или express-validator, в Laravel и ASP.NET Core валидация встроена в фреймворк. Логика от этого не меняется: тип, длина, формат, бизнес-правила — в таком порядке.
Валидация на стороне клиента и сервера: сравнение
Обе проверки описывают одни и те же правила, но решают разные задачи и потому не взаимозаменяемы. Разница по восьми параметрам:
| Параметр | На стороне клиента | На стороне сервера |
|---|---|---|
| Где выполняется | В браузере, до отправки | В приложении, после получения запроса |
| Скорость отклика | Мгновенно, без сети | Один сетевой запрос |
| Можно ли обойти | Да: DevTools, отключённый JS, curl | Нет, это ваш код |
| Роль | Удобство и подсказки | Безопасность и целостность данных |
| Доступ к базе | Нет | Есть: уникальность, остатки, промокоды |
| Чем делается | Атрибуты HTML, Constraint Validation API | Код обработчика или схема (Zod, Joi, express-validator) |
| Что проверяется первым | Значение — тип поля гарантирован | Тип, затем длина, затем формат |
| Можно ли отказаться | Да, форма станет неудобной | Нет |
Чего валидация не закрывает
Распространённое заблуждение: «проверили ввод — защитились от инъекций». OWASP отвечает на это прямо: «Input Validation should not be used as the primary method of preventing XSS, SQL Injection and other attacks». Валидация сужает поток данных, но защита строится на другом:
- SQL-инъекции закрываются параметризованными запросами: текст запроса и значения уходят в базу отдельно, поэтому кавычка в имени остаётся кавычкой, а не частью команды. Конкатенация строк в SQL опасна и с валидацией, и без неё.
- XSS закрывается на выводе, а не на входе. OWASP: «All user data controlled must be encoded when returned in the HTML page to prevent the execution of malicious data».
- Списки разрешённого вместо списков запрещённого. Попытка перечислить опасные символы OWASP называет «a massively flawed approach»; работает обратный подход: «Allowlist validation involves defining exactly what IS authorized, and by definition, everything else is not authorized».
- Существование email регуляркой не проверяется в принципе. OWASP рекомендует базовую проверку формата, а подтверждение — письмом со ссылкой.
Чек-лист: как принимать валидацию на ревью
По этому списку проверяют форму и обработчик перед выкаткой. Каждый пункт либо выполнен, либо нет — промежуточных состояний нет.
- Каждое поле формы имеет
required, корректныйtypeи границы длины (minlength,maxlength). - Сообщения об ошибках выводятся рядом с полем, а не через
alert; у контейнера естьaria-live, у поля —aria-invalid. - Скрипт использует
checkValidity()иvalidity, а не свой набор регулярок поверх готовых правил. - Каждое правило клиентской проверки повторено на сервере. Проверьте это буквально: отправьте запрос через curl мимо формы.
- Серверная проверка идёт в порядке «тип → длина → формат»; строковые методы вызываются только после
typeof x === 'string'. - Задан лимит размера тела запроса и максимальная длина каждого поля.
- Ответ на невалидные данные — статус
400и словарь ошибок по полям. - В базу данные уходят параметризованным запросом; при выводе в HTML экранируются.
- Правила описаны списком разрешённого, а не списком запрещённых символов.
- Регулярка для email проверена на реальных адресах вашей базы, а не взята из чужой статьи.
Последние два пункта проще проверить ревьюером, чем глазами: подойдёт и коллега, и инструмент вроде автоматического код-ревью, которое прогоняет диф по такому же списку. Если хотите разобраться, как ставить подобные проверки в свой рабочий процесс, посмотрите разбор навыков ИИ-агентов Claude и Cursor в разработке.
Обработку пользовательского ввода после валидации держите на очищенных данных: в приложение уходит только то, что вернула функция проверки. Исходный объект запроса дальше обработчика жить не должен.
Частые вопросы про валидацию
Нужна ли валидация на стороне клиента, если есть серверная?
Нужна, но по другой причине. Серверная валидация отвечает за безопасность, клиентская — за удобство: человек видит ошибку сразу, не теряя заполненную форму и не ожидая ответа сервера. OWASP называет сочетание обеих проверок рекомендуемым подходом. Убрать клиентскую можно, но форма станет хуже.
Можно ли обойти валидацию на стороне сервера?
Из браузера — нет. Серверная проверка выполняется в вашем коде, к которому у пользователя нет доступа. Обойти можно только то, что вы не проверили: поле, на которое не написали правило, или маршрут, где забыли вызвать функцию проверки.
Что такое валидация на вход?
Так называют проверку данных в момент, когда они попадают в программу извне: из формы, из запроса к API, из файла или из очереди сообщений. Правило то же самое — данные из внешнего источника считаются недоверенными, пока не проверены.
Почему валидацию на сервере надо начинать с проверки типа?
Потому что структуру тела запроса задаёт отправитель. В поле, которое в форме было текстовым, может прийти объект или массив — в README body-parser об этом предупреждают отдельно. Если сразу вызвать строковый метод, обработчик упадёт с TypeError; если сразу применить регулярку, проверка длины и вовсе не выполнится.
Достаточно ли регулярного выражения для проверки email?
Нет. Регулярка проверяет только формат записи. По формулировке MDN, корректный формат «не обязательно означает, что адрес существует». OWASP рекомендует базовую проверку формата, а существование адреса подтверждать письмом со ссылкой. Кроме того, слишком строгие регулярки отбраковывают живые адреса — в нашем замере типовое выражение из руководств отклонило шесть адресов из семи, включая обычный gmail со знаком плюс и домены .online и .technology.
Защищает ли валидация от SQL-инъекций и XSS?
Не как основная мера. OWASP пишет, что валидация ввода не должна быть главным способом предотвращения XSS и SQL-инъекций. От инъекций защищают параметризованные запросы, от XSS — экранирование данных при выводе в HTML. Валидация дополняет их, но не заменяет.
- Освой нейросеть Perplexity и узнай, как пользоваться функционалом остальных ИИ в одном
- УЧАСТВОВАТЬ ЗА 0 РУБ.
- Расскажем, как получить подписку
- ПОКАЖЕМ, КАК РАЗВЕРНУТЬ МОДЕЛЬ нейросеть DEEPSEEK R1 ПРЯМО НА СВОЁМ КОМПЬЮТЕРЕ