Валидация форм, согласованная с API — остановите плохие данные у источника
Согласованная с API валидация форм сохраняет правила клиента и сервера одинаковыми, чтобы пользователи видели понятные ошибки до отправки и запросы не падали по избежным причинам.

Почему рассинхронизация правил валидации приводит к лишним ошибкам
Плохие данные обычно не выглядят драматично. Это обычные вещи: обязательное поле оставлено пустым, дата введена как «12/13/24», тогда как API ждёт «2024-12-13», номер телефона с буквами или значение из выпадающего списка, которое на бэкенде уже изменили, а в форме — нет.
Если форма принимает такие значения, а API отклоняет их лишь после отправки, пользователи чувствуют себя обманутыми. Они сделали работу, нажали кнопку — и получили размытое «Что-то пошло не так». Иногда ошибка показывается далеко от поля, которое её вызвало, и приходится угадывать.
Несоответствие правил позволяет пройти ряду предсказуемых проблем: обязательные поля пустые или содержащие только пробелы, неверные форматы (email, дата, почтовый индекс), значения вне допустимых границ (слишком длинные, слишком короткие, слишком много элементов), устаревшие enum-значения (старое название тарифа или удалённая роль) и конфликтующие проверки между полями, например дата окончания раньше даты начала.
Цена накапливается быстро. Каждая неудачная отправка может означать брошенную регистрацию, потерянную покупку или частично созданную запись, требующую ручной починки. За этим идут тикеты в поддержку: «Не могу создать аккаунт», хотя на деле виновато простое правило форматирования. Со временем в базу попадают несогласованные записи, отчётность и автоматизация становятся менее надёжными.
Синхронизированная с API валидация форм — простая идея: одинаковые правила выполняются в обоих местах. Форма предотвращает очевидные ошибки и показывает понятный уровеньный фидбэк до отправки. API по-прежнему всё проверяет (браузеру доверять нельзя), но в большинстве случаев лишь подтверждает то, что пользователь уже исправил.
Клиентская и серверная валидация простыми словами
Клиентская валидация выполняется в браузере, пока человек заполняет форму. Это быстрый фидбэк, который ловит очевидные ошибки на раннем этапе: пропущенные обязательные поля, email, который не похож на email, или слишком короткий пароль.
Серверная валидация происходит после отправки формы, когда ваш API получает данные. Это последний защитник, который хранит целостность базы и безопасность пользователей.
Зачем нужны оба уровня
Клиентские правила про скорость и понятность. Они снижают фрустрацию, потому что люди могут чинить ошибки прямо сейчас.
Серверные правила про безопасность и правду. Сервер обязан валидировать, потому что браузер могут обойти (пользовательские скрипты, изменённые запросы, старые версии приложений), сервер видит реальное состояние (существующие аккаунты, разрешения, лимиты) и некоторые проверки требуют доверенных данных (владение, безопасность, целостность).
Распространённое заблуждение — думать, что клиентская валидация «предотвращает плохие данные». Она помогает, но не гарантирует этого. Только API может дать такое обещание.
Настоящая цель: единая книга правил
Нужно иметь одну общую книгу правил: клиент и сервер соблюдают одинаковые требования, чтобы пользователи получали одинаковые ответы в обоих местах.
На практике это значит выбрать один источник правды для правил (часто это схема API или слой валидации на сервере) и заставить клиент ей соответствовать. Если сервер требует пароль от 12 символов и отбрасывает распространённые пароли, форма должна заранее указывать «12+ символов». Проверку на «распространённый пароль» можно показывать как подсказку, но жёсткое требование должно быть согласовано.
Что и где стоит валидировать
Некоторые проверки уместны в браузере, потому что помогают человеку завершить форму. Другие проверки обязательно на сервере, потому что только там можно достоверно это проверить. Цель — согласованность: пользователь должен узнавать о проблеме до того, как запрос упадёт, а когда сервер отклоняет запрос, причина должна совпадать с тем, чему UI уже учил.
Начните с правил «формы» поля. Эти базовые вещи должны совпадать везде:
- Обязательное поле или нет
- Ожидаемый тип (текст, число, дата)
- Формат, который ждёт API
Если API ждёт число, а форма шлёт текст, запрос упадёт, даже если UI выглядел нормально.
Ограничения тоже нужно делить: минимальная и максимальная длина, допустимые значения (например список кодов стран) и простые шаблоны (например «должно содержать @»). Клиент даёт быстрый фидбэк; сервер подтверждает.
Перекрёстные правила требуют внимания, потому что затрагивают несколько полей. Если пользователь может исправить проблему, сравнив два поля на одном экране, валидируйте это в форме и повторите проверку на сервере. Два распространённых примера: «пароль и подтверждение должны совпадать» и «дата начала должна быть раньше даты окончания».
Некоторые правила — только на бэкенде, потому что клиент не может их надёжно проверить: проверки уникальности (записан ли уже email?), права доступа (может ли пользователь установить это значение?), ограничения по частоте, всё, что зависит от текущего состояния БД, и правила безопасности (отклонять неожиданные поля или значения).
Форма регистрации может локально проверить формат email, но только API подтвердит, занят он или нет.
Как правила валидации расходятся со временем
Дрейф валидации часто начинается с хороших намерений: кто-то быстро исправил баг, выпустил и забыл. Через неделю другой человек добавил «временное» правило в другом файле. После нескольких таких итераций UI и API начинают расходиться, и пользователи получают запутанные ошибки.
Копипаст валидации — частая причина. Команда строит три формы, собирающие одно и то же поле (email, телефон, название компании), но каждая форма получает чуть разные правила. API может требовать более строгого формата, тогда как одна из форм просто проверяет «не пусто».
Изменения версий — ещё один источник. Бэкенд обновил правило (минимум пароля с 8 на 12, или поле стало обязательным), а UI остался на старом предположении. Пользователи проходят клиентскую валидацию и всё равно получают ошибки от сервера.
Различия окружений могут скрывать дрейф до тех пор, пока он не начнёт мешать. В staging могут быть более мягкие настройки, другие feature-флаги или иная конфигурация провайдера аутентификации. В тестах всё выглядит нормально, а в продакшене настоящие входные данные отклоняются, потому что правила строже.
Код, сгенерированный ИИ, может усугубить это, дублируя проверки по компонентам, смешивая библиотеки или придумывая регулярные выражения, которые не совпадают с API. Часто можно увидеть одно и то же поле, проверяемое тремя разными способами в одном приложении.
Вероятно, у вас есть дрейф, если вы наблюдаете такие шаблоны:
- Пользователи жалуются «сначала ОК, потом падает» после нажатия Отправить
- Одно и то же поле ведёт себя по-разному на разных страницах
- Ошибки отображаются как общий «Что-то пошло не так»
- Исправления вносятся в UI без обновления контракта API (или наоборот)
Пошагово: делаем API источником правды
Если пользователь может заполнить форму, которую ваш API отклонит, вы получите пустые запросы и запутанные ошибки. Решение простое: принимайте контракт API как источник правды и заставьте UI ему соответствовать.
Начните с документации контракта для нужного endpoint'а. Для каждого поля зафиксируйте:
- Тип
- Обязательно или опционально
- Допустимые диапазоны и лимиты
- Форматы (email, UUID, date)
- Ограничения, например max length или список допустимых значений
Затем разделите правила на две группы. Первая — то, что браузер может проверить мгновенно (обязательность, формат, длина). Вторая — то, что знает только сервер (уникальность, права, лимиты). Это даёт быстрый фидбэк, не претендуя, что клиент гарантирует состояние сервера.
Практический путь, который выдержит время:
- Выберите формат схемы, который можно переиспользовать (OpenAPI, JSON Schema или общая библиотека валидации).
- Опишите правила запроса и ответа endpoint'а в этой схеме, включая ограничения.
- Генерируйте или переиспользуйте клиентские валидаторы из схемы, чтобы правила UI не копировались вручную.
- Валидируйте на сервере используя ту же схему (или эквивалентные правила) перед бизнес-логикой.
- Стандартизируйте коды ошибок и шаблоны сообщений, чтобы одна и та же проблема выглядела одинаково везде.
Также делайте ошибки предсказуемыми. Используйте стабильные коды ошибок (например email_taken или password_too_short) и позвольте клиенту и серверу отображать их как понятный текст.
Проектирование сообщений об ошибках, которые можно исправить
Люди исправляют ошибки быстрее, когда сообщение говорит точно, что изменить. Цель — согласованность: одно и то же правило должно давать одно и то же сообщение, поймано ли оно в браузере или отклонено API.
Пишите одно понятное предложение на правило и держите его стабильным. Если правило меняется, обновляйте сообщение одновременно. «Пароль должен быть не короче 12 символов» — реально полезно. «Неверный ввод» — нет.
То, где вы показываете ошибки, так же важно, как и то, что вы говорите. Размещайте сообщение рядом с полем, требующим внимания. Добавляйте краткое резюме вверху только если это помогает найти пропущенные поля, особенно на длинных формах.
Надёжный шаблон для большинства форм:
- Встроенное сообщение под полем (короткое и конкретное)
- Стили, подчёркивающие конкретный ввод
- Сверху краткий список из 1–3 проблем (без дубликатов)
- Фокус устанавливается на первую ошибку после отправки
С серверными ошибками нужно обращаться особенно аккуратно. Даже при хороших клиентских проверках запросы будут падать по причинам, которыми клиент не управляет: истёкшая сессия, условия гонки или правила, о которых UI не знал. Когда API отвечает ошибками, сопоставляйте их с полями по стабильным ключам и переиспользуйте те же формулировки. Если ошибка не относится к одному полю, показывайте её как сообщение уровня страницы.
Избегайте технических формулировок в текстах для пользователей. Люди не должны видеть «400 Bad Request» или сырые коды валидации. Переведите их в простой язык: «Этот адрес email уже используется» или «Пожалуйста, введите корректную дату».
Перекрёстные проверки и асинхронные проверки без путаницы
Некоторая валидация не про одно поле, а про то, как поля связаны. Если проверять только каждое поле по отдельности, пользователи могут отправить данные, которые потом упадут на сервере, и ошибка покажется случайной.
Перекрёстная валидация покрывает ожидаемые проверки: «дата окончания позже даты начала» или «пароль и подтверждение совпадают». Простое правило: если пользователь может исправить проблему, сравнив поля на одном экране, валидируйте это в форме и объясните прямо рядом с полями.
Держите сообщение привязанным к действию. «Даты неверны» — расплывчато. «Дата окончания должна быть позже даты начала» — понятно, что менять.
Асинхронные проверки: когда нужно спрашивать сервер
Некоторые проверки зависят от реальных данных или прав, поэтому требуют сервера. Общие примеры: «email уже используется», «код приглашения действителен» или «имя пользователя доступно». Такие проверки всё равно должны быть частью согласованной системы валидации, но UI должен обрабатывать их аккуратно, чтобы не мешать набору текста.
Простой подход:
- Показывайте небольшой статус «Проверяем…» после паузы в наборе или при уходе из поля.
- Позвольте пользователю продолжать заполнять форму, пока проверка идёт.
- Если проверка провалилась, объясните, что делать дальше (использовать другой email, запросить новый код приглашения).
- Если проверку нельзя выполнить (оффлайн или таймаут), не притворяйтесь, что всё в порядке — попросите повторить на отправке.
Когда валидировать: при blur, при submit или прогрессивно
Время имеет значение. Валидация на каждый ввод может выглядеть как спор с пользователем.
Простой дефолт, который подходит многим командам:
- Проверяйте базовый формат и минимальную длину по мере ввода.
- Перекрёстные правила проверяйте, когда второе поле затронуто (подтверждение пароля) или при blur.
- Асинхронные проверки запускайте при blur или после короткой паузы, а не на каждое нажатие клавиши.
- Повторно проверяйте всё при отправке и сопоставляйте серверные ошибки с правильными полями.
Типичные ошибки, которые создают «шумные» отказы
Шумные отказы кажутся случайными пользователю, но обычно происходят из нескольких предсказуемых ошибок. Главная тема — дрейф: UI и API постепенно начинают применять разные правила.
Доверие только клиентским проверкам — классическая ловушка. Пользователь может обойти браузер, или API может вызываться из другого клиента. Когда сервер становится первым местом проверки, люди видят ошибки только после того, как потратили время на заполнение формы.
Шаблоны, которые создают больше всего вопросов «почему это упало?»:
- Одно и то же поле имеет разные правила на разных экранах (регистрация vs настройки).
- Регулярные выражения блокируют реальные вводы, например имена с апострофами, неанглийские символы или номера квартир в адресе.
- API возвращает общий 400/500, и UI не может подсветить проблемное поле.
- Правила API меняются, а UI-тесты и QA-скрипты всё ещё ожидают старое поведение.
- Ошибки написаны для разработчиков («constraint violation») вместо пользователей («Пароль должен быть не короче 12 символов»).
Простой пример: UI принимает «Sam O’Neil» в профиле, а API отклоняет, потому что серверный regex позволяет только A–Z. Пользователь видит «Запрос не удался» без подсветки поля, пробует снова и сдаётся.
Снижение шума часто сводится к нескольким фокусным исправлениям: стандартизировать правила для каждого поля на всех экранах, ослабить шаблоны, чтобы они соответствовали реальному вводу, и вернуть специфичные коды ошибок, чтобы UI мог поместить сообщение прямо там, где это нужно.
Быстрая чек-лист проверок перед релизом
Перед выпуском пройдитесь по полному пути: что вводит пользователь, что блокирует UI, что отклоняет API и какое сообщение при этом видит пользователь.
Сравните требования API с формой. Если API говорит, что поле обязательно, UI должен явно пометить его как обязательное и не позволять отправить пустую форму. Если UI требует чего-то, чего API не требует, пользователи могут застрять без причины.
Используйте небольшой набор повторяемых тестов для каждого поля:
- Совпадение правил обязательности: каждое обязательное API-поле обязательно и в UI; опциональные поля действительно опциональны.
- Попробуйте три плохих значения для поля: слишком короткое, слишком длинное, неверный формат.
- Прогон одного перекрёстного правила насквозь: клиентское сообщение и серверное должны совпадать по смыслу.
- Убедитесь, что API возвращает ошибки по полям, а не только одну общую строку.
- Убедитесь, что UI отличает сетевые ошибки от ошибок валидации (таймаут не должен выглядеть как «ваш email неверен").
Практический совет: сделайте хотя бы одну реальную отправку с открытыми DevTools. Если сервер отклоняет то, что UI позволял, — есть дрейф.
Реалистичный пример: форма регистрации, которая постоянно падает
Команда выкатывает страницу регистрации с четырьмя полями: email, пароль, название компании и код приглашения. В браузере всё выглядит нормально. Но поддержка получает массу тикетов: «Постоянно выскакивает ошибка».
Корень проблемы — несоответствие. UI проверяет, что пароль минимум 8 символов. API требует минимум 12. Пользователь вводит Sunshine1 (9 символов), форма показывает зелёную галочку, запрос уходит, API возвращает 400. Приложение превращает это в общий баннер наверху, и пользователь не знает, что исправлять.
Признаки легко заметить:
- Много неудачных запросов сразу после отправки
- Общие баннеры ошибок вместо подсказок по полям
- Пользователи снова и снова пробуют одни и те же данные и застревают
Исправление не в «добавлении ещё проверок». Надо принять контракт API как источник правды и генерировать клиентские правила из него.
Простой план восстановления:
- Согласуйте реальное правило (8 или 12) с командой, владеющей API.
- Обновите контракт API (например, OpenAPI schema) с этим правилом.
- Перегенерируйте или обновите клиентскую валидацию из контракта.
- Сопоставьте ошибку API с полем пароля и покажите понятное сообщение: «Пароль должен быть не короче 12 символов.»
- Перетестируйте поток, включая короткий пароль и неверный код приглашения.
После этого число плохих запросов падает: пользователи получают фидбэк до отправки. Если код приглашения проверяется только на сервере, UI всё равно может помочь: валидировать формат локально (длина, допустимые символы), а сообщение «Код приглашения не найден» показывать только после подтверждения от сервера.
Следующие шаги, чтобы держать валидацию согласованной со временем
Выберите одну форму с высоким трафиком и сделайте её пилотом по согласованию. Подойдёт то, что чаще ломается или важно для дохода: регистрация, оплата или сброс пароля. Поставьте рядом правила API (типы, обязательность, min/max длина, допустимые значения) и правила формы, и сведите разрывы, пока одни и те же входы не проходят или не падают в обоих местах.
Когда одна форма заработает корректно, переходите к следующей вместо попытки починить всё сразу. Маленькие победы легче проверить, и они быстро снижают нагрузку в поддержку.
Добавьте лёгкий обзор контракта при изменениях API
Дрейф валидации происходит, когда API развивается, а UI нет. Добавьте один шаг в ваш обычный процесс изменений: когда меняется форма запроса или ответа, кто-то проверяет, совпадают ли правила формы и сообщения об ошибках.
Держите рутину короткой:
- Отметьте, какие вводы стали строже или мягче.
- Обновляйте серверные и клиентские правила в одном pull request.
- Проверьте, что сообщения об ошибках соответствуют названиям полей, видимым в форме.
- Выполните один тест с плохим вводом намеренно (пустое обязательное поле, слишком длинная строка, неверный формат).
Если кодовая база в беспорядке — начните с аудита
Если приложение было сгенерировано ИИ или унаследовано от прототипа, рассогласование валидации часто симптом более серьёзных проблем: дубли правил в нескольких файлах, недоделанные потоки авторизации или небезопасные шаблоны вроде доверия только клиентским проверкам.
Если нужен внешний обзор, FixMyMess (fixmymess.ai) специализируется на диагностике и ремонте приложений, сгенерированных ИИ, включая восстановление связи между клиентской и серверной валидацией, починку сломанных механизмов аутентификации и повышение уровня безопасности при переходе прототипов в реальный трафик.
Часто задаваемые вопросы
Почему моя форма выглядит валидной, но всё равно падает после нажатия «Отправить»?
Когда форма пропускает значение, а API его отклоняет, пользователь тратит время и чувствует себя обманутым. Ремонт — сделать правила API источником правды и заставить UI отражать эти же правила до отправки.
Нужны ли мне и клиентская, и серверная валидация?
Клиентская валидация даёт быстрый и понятный отзыв во время ввода. Серверная — это окончательная инстанция, которая защищает данные, потому что любой может обойти браузер, и только сервер знает текущее состояние (существующие аккаунты, права и т. п.).
Какие правила валидации всегда должны совпадать между UI и API?
Начните с «формы» поля: обязательность, тип, формат и простые лимиты (минимальная/максимальная длина). Эти правила легко согласовать и они предотвращают большинство излишних ошибок при отправке.
Какие проверки валидации должны выполняться только на сервере?
Проверки, которые зависят от живого состояния сервера, должны оставаться на сервере. Примеры: «email уже занят», проверки прав доступа, ограничения по частоте и всё, что зависит от текущей БД.
Как писать сообщения об ошибке, которые пользователи действительно смогут исправить?
Размещайте сообщение рядом с полем, которое нужно исправить, и говорите точно, что изменить. Используйте одни и те же формулировки вне зависимости от того, поймала ли ошибку браузер или API, чтобы пользователь не получал противоречивых сигналов.
Как UI может выделить правильное поле, когда API отклоняет ввод?
Просите API возвращать стабильные коды ошибок и идентификаторы полей, а не одну общую строку. Тогда UI сможет сопоставить каждую ошибку с нужным полем и показать то же сообщение, что и при клиентской проверке.
Как лучше обрабатывать перекрёстные правила, например даты начала/окончания?
Валидация перекрёстных полей должна выполняться в форме, как только доступно второе поле, и повторно проверяться на сервере. Формулируйте сообщение так, чтобы оно описывало связь, например: «Дата окончания должна быть позже даты начала».
Как обращаться с асинхронными проверками вроде «email уже используется», чтобы не раздражать пользователей?
Запускайте асинхронные проверки после небольшой паузы или по событию blur, не на каждый ввод. Если проверка не удалась (таймаут или офлайн), честно сообщите и попробуйте снова при отправке, не делая вид, что значение точно верное.
Почему правила валидации со временем расходятся?
Правила расходятся из-за копипаста в нескольких формах, изменения backend без обновления UI и различий между staging и production. Код, сгенерированный ИИ, часто усугубляет это, дублируя валидаторы или смешивая библиотеки.
Как быстро исправить рассогласования в валидации в запутанном или AI-сгенерированном приложении?
Выберите одну важную форму с высоким трафиком и сравните требования API и проверяемые правила UI, затем протестируйте несколько некорректных значений целенаправленно. Если кодовая база грязная или сгенерирована ИИ, полезен целенаправленный аудит: находят дубли, несоответствия и отсутствие ошибок по полям — и исправляют их.