Контракт фронт-бэк помогает фронту и бэку не расходиться при частых релизах: типы, генерация клиента, проверки схем в CI и правила версий.

Рассинхронизация фронта и бэка - это когда интерфейс ожидает одно, а сервер на деле отдает другое. Обычно это выглядит как «вчера работало, сегодня сломалось»: кнопка не делает действие, список пустой, форма не отправляется, а в консоли внезапно 400 или 500.
Чаще всего проблема не в «кривых руках», а в скорости и параллельных изменениях. Фронтенд уже начал использовать новое поле, а бэкенд еще не выкатил его. Или бэкенд переименовал параметр, а фронт продолжает слать старый. Чем чаще релизы, тем больше таких точек: больше мелких правок, меньше времени на ручные созвоны и проверку «на глаз».
В интерфейсе это обычно проявляется одинаково: пропадают данные (поле переименовали или сделали вложенным), ломается валидация (сервер внезапно требует новое обязательное поле), статусы и ошибки становятся «не теми», а логика начинает вести себя странно из-за смены типа (например, было число, стала строка).
Цена этих «мелочей» почти всегда выше, чем кажется: срочные правки перед релизом, ночные откаты, горячие фиксы, разбор «кто виноват», а главное - недоверие к выкладкам. Команда начинает реже релизиться и чаще «замораживать» изменения.
Выход простой: нужен контракт фронт-бэк, который описывает, какие запросы и ответы считаются правильными. Но одного документа мало. Его нужно проверять автоматически, чтобы несовпадения ловились в CI до того, как их увидят пользователи.
Контракт API - это понятное обеим сторонам обещание о том, как фронт и бэк общаются между собой. Проще говоря, это договор: какие запросы можно отправлять, какие ответы придут, и как выглядят ошибки.
Контракт отвечает на практичные вопросы: как называется эндпоинт, какие параметры он принимает, какие поля вернет, какие из них обязательные, в каком формате даты и числа, какие бывают статусы и ошибки. Когда контракт записан и доступен всем, фронт не гадает, а бэк не объясняет на словах.
Обычно в контракте фиксируют пути и методы (GET/POST и т.д.), поля и их типы, обязательность и значения по умолчанию, коды ответов и структуру ошибок, а также ограничения вроде «максимум 50 элементов на страницу».
При этом контракт не про дизайн экранов и не про внутренности сервера. Он не обязан описывать таблицы в базе, микросервисы и архитектуру. Он только про обмен данными.
Ломается чаще всего самое простое: поле переименовали, удалили, поменяли тип, изменили смысл значения. Например, фронт ждет price как число, а бэк начинает отдавать строку "1 200" или переносит поле внутрь другого объекта. Сборка может пройти, а на проде пользователи увидят пустые блоки и ошибки, которые трудно быстро объяснить.
Контракт можно оформить по-разному. Важнее не «самый модный» формат, а единый источник истины, на который реально опирается команда.
OpenAPI удобен для классического REST: эндпоинты, методы, параметры, коды ответов, примеры. Это хороший выбор, если вы регулярно добавляете ручки и хотите, чтобы всем было понятно: куда идти, что передавать и что придет в ответ.
Пример: вы добавляете GET /orders и вводите фильтр по статусу. В OpenAPI легко описать новый query-параметр, и фронту сразу понятно, что отправлять, а бэку - что принимать.
JSON Schema полезна, когда главная боль - структура данных и строгая валидация payload. Ее часто используют для событий, конфигов и больших форм. Еще один частый сценарий - держать общие определения сущностей (например, Address, Money) и валидировать их на входе.
Иногда JSON Schema используют внутри OpenAPI, чтобы правила не размазывались по коду.
gRPC и Protobuf подходят, когда нужен максимально строгий контракт и предсказуемая совместимость: сервисы общаются часто, важны типы, и ошибки «не то поле, не тот тип» должны ловиться как можно раньше.
Обычно достаточно одного главного артефакта:
Важно закрепить это как правило проекта: контракт хранится рядом с кодом и меняется через PR. Тогда и фронт, и бэк ориентируются на один документ, а не на фрагменты переписки.
Контракт работает, когда он живет рядом с кодом и меняется по понятным правилам. Тогда у команды одна версия правды.
Сначала решите, будет ли одна схема на весь продукт или несколько. Одна общая OpenAPI схема проще для небольших сервисов. Разделение по модулям (например, auth, billing, catalog) удобнее, когда части развиваются с разной скоростью или за них отвечают разные люди. Главное - не смешивать подходы хаотично: либо единый файл со строгими разделами, либо несколько схем с понятными границами.
Храните контракт в репозитории вместе с кодом. Так история изменений видна в коммитах, можно делать ревью, а релизы получают конкретную версию схемы. Практично держать контракт рядом с бэком, но с обязательным ревью со стороны фронта. Если фронт и бэк в разных репозиториях, все равно нужна единая точка истины и синхронные обновления.
Владелец контракта почти всегда совместный. Бэкенд отвечает за техническую корректность и безопасность, фронтенд - за удобство данных для UI. Если оставить владельцем только одну сторону, вторая начнет обходить правила: бэк будет «чуть-чуть менять» ответы, а фронт - «временно парсить как получится».
Хорошее правило процесса:
Если нужно добавить поле в ответ профиля, сначала добавьте его в схему как необязательное, затем выкатите бэк, и только потом начните использовать поле на фронте. Даже при частичном релизе ничего не ломается.
Если API уже работает, проще всего ввести контракт поверх текущей реальности. Цель первых 1-2 спринтов - не идеальная схема, а предсказуемые изменения и быстрый сигнал о поломках.
Начните с инвентаризации: какие ручки реально использует фронт, какие поля обязательны, где есть «магические» значения. Часто оказывается, что часть эндпоинтов устарела, а часть полей фронт вообще не читает.
Дальше идите по шагам:
Зафиксируйте текущее API в схеме: пути, методы, коды ответов, примеры, модели данных. Не пытайтесь описать все сразу - возьмите 10-20% самых важных запросов.
Подключите генерацию типов и клиента для фронта из этой схемы. Тогда вместо ручных интерфейсов фронт получает актуальные типы и подсказки в редакторе.
Добавьте проверки в CI: схема должна валидироваться и быть согласована с тем, что лежит в репозитории. Плюс проверка «в схеме нет ломающих изменений» на уровне diff.
Договоритесь о правилах совместимости. Например: нельзя удалять поля и менять их типы без новой версии. Безопаснее добавлять только необязательные поля.
Введите ревью изменений схемы как отдельный шаг: любое изменение API начинается с правки схемы и обсуждения влияния на фронт.
Пример: бэкенд хочет заменить price (число) на объект с валютой. По правилам вы сначала добавляете priceV2, оставляете старое поле, обновляете генерацию клиента, переводите фронт постепенно, а потом удаляете старое только в новой версии.
Когда фронт пишет запросы «на глаз», рассинхронизации почти неизбежны: поля называются по-разному, типы плавают, ошибки обрабатываются как попало. Генерация клиента сильно снижает риск, потому что фронт получает готовые методы, модели и единый способ разбирать ответы.
На практике это дает типы для входных и выходных данных, список доступных методов, базовую сборку URL и заголовков, а также более предсказуемую обработку ошибок. Контракт превращается не в «договоренность в чате», а в код, который не даст случайно отправить не то поле или забыть обязательный параметр.
Обычно выбирают один из подходов: генерация по OpenAPI (для REST), работа с JSON Schema для отдельных моделей/событий или Protobuf для gRPC.
Чтобы генерация не превращалась в хаос, закрепите правила: фиксируйте версию генератора в репозитории, складывайте сгенерированный код в отдельную папку, не правьте его руками, обновляйте схему маленькими PR и прогоняйте сборку на каждый PR.
Безопасный порядок обновлений почти всегда один и тот же: сначала меняется схема, затем генерация клиента, затем правки во фронте. Если diff внезапно на сотни строк, скорее всего вы поменяли не только контракт, но и сам инструмент генерации.
Если контракт хранится в виде файла (например, OpenAPI схема), логично проверять его там же, где проверяется код: в CI. Тогда несовпадения ловятся в момент пулл-реквеста, а не на проде.
Первый уровень простой: схема должна быть корректной. CI прогоняет валидатор и убеждается, что документ парсится, ссылки и типы на месте, а генерация клиента действительно проходит.
Дальше полезно сделать генерацию частью сборки: если схема поменялась, клиент пересобирается автоматически. Так фронт не живет на догадках, а работает с тем, что объявлено в контракте.
Вторая проверка - дифф схемы относительно основной ветки. CI должен показать изменения и подсветить риск. Удобно делить изменения на совместимые и ломающие.
Ломающими обычно считаются такие вещи: удалили endpoint или поле ответа, поменяли тип или формат, сделали поле обязательным, сузили набор допустимых значений (enum), изменили смысл без изменения названия.
Дальше включается правило: запрет ломающих изменений без версии. То есть CI падает, если в контракте есть breaking change, но вы не подняли версию API и не оставили обратную совместимость (через новый endpoint/поле).
В типичном пайплайне это выглядит так: валидируем схему, сравниваем с основной веткой, блокируем breaking changes без версии, генерируем клиент и собираем фронт с новым клиентом.
Большинство рассинхронизаций происходит из-за маленьких изменений, которые кажутся безопасными. Особенно в быстром цикле, когда бэк уже задеплоили, а фронт еще в ревью или, например, ждет модерации.
Самые частые ошибки:
"123" стало 123), и в интерфейсе появляются странные значения или падают преобразования.Пример: на бэке статус заказа расширили с created/paid до created/paid/refunded. Бэк прав, но фронт не знает refunded и продолжает рисовать «Оплачен», потому что fallback не предусмотрели.
Несколько привычек сильно помогают. Удалять и менять типы опаснее всего, поэтому чаще выигрывает стратегия «добавили новое, старое поживет». На фронте стоит быть терпимее к неизвестным данным: неизвестный enum лучше отправить в «Другое», чем ломать экран. И отдельно полезно договориться о стабильном контракте ошибок: одинаковая форма, одинаковые ключи, понятные коды.
Если фронт уже работает с API, бэк не меняет поведение так, чтобы старый клиент начал падать. Это снижает аварии после релиза и убирает вечное «у меня на стенде все ок».
«Не ломаем» на практике означает: без версии делаем только обратно совместимые изменения. Добавлять можно почти всегда, а вот переименовать поле, изменить тип, убрать значение из enum или поменять смысл статуса - это уже ломает старых клиентов.
Чтобы правило работало, зафиксируйте границы:
Деприкация помогает менять контракт без стресса. Например: сегодня добавили phoneNormalized, старое phone пометили устаревшим, дали 2-4 недели на переход и удалили только в новой версии.
Changelog по контракту должен быть коротким, иначе его никто не читает. Достаточно: что изменилось, совместимость, срок деприкации/удаления и кто отвечает.
Команда делает страницу профиля. На фронте поле одно: phone, строка. На бэке решили хранить несколько телефонов и поменяли ответ на phones: string[]. В быстром цикле это легко заканчивается тем, что фронт внезапно получает вместо строки массив, падает форматирование, и пользователи видят пустое поле или ошибку.
С контрактом переход делается спокойно: сначала добавляем новое, не ломая старое. Бэк выпускает изменение как расширение ответа: оставляет phone и добавляет phones[]. В контракте это новое необязательное поле, а старое помечается как устаревшее, но продолжает работать.
Фронт переезжает в два шага: сначала читает phones[0], а если массива нет, берет старое phone. После того как старые клиенты почти исчезли, поддержку phone убирают по договоренности и только в новой версии.
Самое важное - это ловится до релиза. В CI лежит проверка совместимости схемы: пайплайн сравнивает прошлую и новую схему и ругается, если вы удалили phone или изменили его тип. Параллельно запускается генерация клиента и сборка фронта. Если типы не сходятся, ошибка появляется в сборке, а не у пользователей.
Полезно заранее договориться, кто смотрит такие изменения: бэкенд отвечает за схему и семантику, фронтенд - за то, что клиент сгенерирован и не требует костылей, тестирование - за базовые сценарии и крайние случаи.
Автоматизируйте то, что ломается чаще всего, и начните с малого:
Если вы собираете продукт в TakProsto (takprosto.ai), удобно держать контракт рядом с кодом и менять его через PR так же, как остальную логику. А режим планирования и снапшоты с откатом помогают спокойно проверять спорные изменения в API и возвращаться назад без ручной раскопки истории.
Главное - сделать контракт частью ежедневного цикла: поменял код, обновил схему, прогнал проверки, и только потом вливаешь.
Это когда UI отправляет запросы или ожидает поля по одной логике, а сервер уже живет по другой. В итоге часть данных «исчезает», формы начинают получать 400, а обработка ошибок становится непредсказуемой.
Потому что изменения идут параллельно и мелкими порциями: фронт уже начал использовать новое поле, а бэк еще не выкатил его (или наоборот). Чем чаще релизы, тем больше точек, где можно случайно переименовать поле, поменять тип или сделать параметр обязательным.
Начните с описания 10–20% самых важных ручек, которые реально дергает интерфейс: авторизация, профиль, ключевые списки и формы. Этого хватает, чтобы быстро поймать самые болезненные поломки, не пытаясь за один раз задокументировать весь API.
Если у вас REST и веб-фронт, чаще всего проще взять OpenAPI как единый источник истины. JSON Schema удобна, когда боль именно в строгой структуре payload и валидации больших объектов, а Protobuf обычно выбирают для gRPC и максимально строгих типов между сервисами.
Минимальный набор — пути и методы, параметры запроса, структура ответа, типы и обязательность полей, коды ответов и единый формат ошибок. Важно фиксировать и «мелочи» вроде формата дат и ограничений пагинации, потому что именно они часто ломают UI.
Самое частое — удаление или переименование поля, изменение типа (число стало строкой или объектом), превращение необязательного поля в обязательное, смена формата ошибок и неожиданные новые значения enum. Эти изменения могут пройти ревью, но ломают старые клиенты сразу после выкладки.
Сначала добавляйте новое без поломки старого: новое поле или новый эндпоинт, старое оставляйте работать. Затем обновляйте бэк, после этого — фронт, и только потом убирайте старое в новой версии, когда переход завершен.
Потому что фронт перестает «угадывать» руками: методы, модели и типы приходят из схемы, и ошибку видно на этапе сборки. Если вы забыли обязательное поле или перепутали тип, это проявится сразу в коде и тестах, а не в виде пустого экрана у пользователя.
Хотя бы проверяйте, что схема валидна и из нее реально собирается клиент. Следующий шаг — проверка диффа на ломающие изменения: удаленные поля, измененные типы, новые обязательные поля; такие изменения должны блокироваться без поднятия версии или без обратной совместимости.
Держите контракт рядом с кодом и меняйте его через PR с ревью фронта и бэка, чтобы была одна версия правды. В TakProsto удобно поддерживать это как привычку: правите схему и код вместе, а снапшоты и откат помогают безопасно проверять спорные изменения и быстро возвращаться назад, если что-то пошло не так.