api-contract-check: валидаторы API из TS-типов, рантайм меньше 1 КБ
PrimeCoder · 2026 · формат: tech
Разбор новинки простыми словами: что это, кому нужно и брать ли.
Валидацию ответов бэкенда можно строить из уже написанных TS-типов, минуя тяжёлый OpenAPI-пайплайн — это снимает рутину перегенерации контрактов у фронтенд-команд.
Связанные услуги: каталог, AI Boost Team, стать клиентом.
Валидация, которая появляется поздно или никогда
Валидация ответов бэкенда редко планируется. Она появляется после первого инцидента: бэкенд обещал строку, прислал null, форма упала у клиента, поддержка получила три письма. До этого момента вызовы fetch живут в useEffect и никто их не проверяет.
В большинстве проектов, которые видел, валидация не производилась вообще — вызовы XHR / fetch выполнялись в componentDidMount / useEffect или аналогах для других фреймворков.
Автор библиотеки api-contract-check Дмитрий Казаков (DmitryKazakov833) выложил в open source инструмент, который закрывает эту дыру без отдельного проекта и без согласований с бэкендом. Логика: DTO-модели на TypeScript на фронте уже написаны, значит валидаторы можно собрать из них на этапе сборки. Не заново писать схемы, не ждать npm-пакет со спецификацией, не спорить в чате о том, чья версия контракта правильная.
Библиотека входит в набор @arfa-tools/generator, документация лежит на русском и английском. Материал свежий: на момент выгрузки сигнала статья провисела на Хабре около получаса, заявленное время прочтения — 7 минут, охват — 872 читателя. То есть это не отраслевая сенсация, а рабочий инструмент, который автор вынес из собственной практики.
Что именно вы получаете
Два модуля с разными задачами.
Сборочный. Функция createValidators принимает список файлов с TS-моделями, путь к tsconfig и имена типов, которые описывают запрос и ответ. На выходе — обычный JS-объект со схемой. Автор приводит пример: для файла getUser схема выглядит как { request: { id: 'string' }, response: { name: 'string' } }. Ничего магического, никакого бинарного формата.
Дальше с этим объектом делайте что хотите: сериализуйте через JSON.stringify и положите в файл, который подгружается лениво; конвертируйте в схему Zod или аналога; выводите в документацию; держите прямо в коде вызова. Схема — данные, а не запечатанный артефакт.
Рантайм. Модуль проверяет запрос и ответ по схеме, вычищает лишние поля и складывает их через getExtraneous в лог. Вес — меньше 1 КБ в сжатом виде. Для сравнения: любой популярный валидатор форм в бандле тянет на порядок больше, даже если вы используете три поля из его API.
Все лишнее будет очищено и залогировано, а необходимое — проверено на соответствие TS-типам.
Есть колбэк getDegraded. Он срабатывает, когда конструкцию TypeScript не удалось разобрать до конца и схема получилась ослабленной. Полезная штука: вместо тихой деградации вы видите список имён и решаете, чинить тип или принять риск. Автор отдельно отмечает собственный syntax interpreter для разбора TS AST — по его бенчмаркам он быстрее ts.createProgram, oxc-parser и SWC. Построение схемы для файла с графом из 100 импортов — около 2 мс. Цифра author-side, но порядок величин понятен: это не минуты на CI.
Откуда взялась проблема: короткая история трёх подходов
Сначала в энтерпрайзе придумали генерировать всё из OpenAPI. Бэкенд отдавал спецификацию, из неё рождались TS-типы или ready-made клиенты вида api.getUser. Звучит стройно. На практике всё ломается об организацию: спецификация расходится с реальными ответами (при param?: string прилетает null), версионирование пакетов превращается в rebase-марафон несколько раз в день, фронт не может начать параллельно с бэком, а монолитный клиент не поддаётся tree-shaking — на главной нужен один эндпоинт, в бандл едут все.
Потом появился ручной путь: Zod, Valibot, схемы пишут руками на фронте. Удобно, пока проект небольшой. Дальше выясняется, что типы в проекте живут в двух видах — написанные руками и выведенные через infer, — и это путает. Что схемы тяжелее, чем нужно для проверки контракта. Что typecheck от вывода типов ползёт медленнее.
Третий путь — генерация из TS-типов — не новый. Но существующие решения не затачивались под API: не чистят лишние поля, не логируют их, не всегда дают сериализуемый результат.
| Подход | Кто платит | Главный риск |
|---|---|---|
| Генерация из OpenAPI | Инфраструктура и организационные согласования | Схема расходится с реальным ответом, клиент монолитный |
| Ручные схемы (Zod, Valibot) | Время разработчика на каждое поле | Два источника типов, размер бандла |
| Валидаторы из TS-типов | Один раз настроить сборку | Неполный разбор сложных конструкций TS |
Чем это отличается от ts-interface-builder и typia
Похожие инструменты на рынке есть, автор сам их называет. ts-interface-builder генерирует отдельные файлы с валидаторами, typia правит код плагином бандлера. Разница в фокусе: api-contract-check собран вокруг конкретной задачи — проверить запрос и ответ на границе с бэкендом, вычистить лишнее, залогировать вычищенное, отдать сериализуемую схему.
Последний пункт недооценивают. Если схема — обычный JS-объект, её можно скормить другой библиотеке проверки или вывести в документацию без конвертеров и переходников. Это снимает главный страх при выборе валидатора: «а если через год захотим переехать». Переезд стоит один JSON.stringify.
Кому это нужно на самом деле
Не всем. Разберём по ситуациям.
- Фронтенд-команда из 2–10 человек, бэкенд отдельный. Основной сценарий. Контракты меняются, спецификация либо устарела, либо её нет. Валидаторы из типов ставятся за один вечер, дальше работают молча.
- Интеграция с внешними API. Когда чужая система присылает что хочет, а искать рассогласование приходится по логам через неделю после инцидента. Точка проверки на входе экономит дни.
- Проект на своём бэкенде с одним фронтом. Можно, но выгода меньше: у вас и так один источник правды, а типы обычно шарятся пакетом.
- Энтерпрайз с выстроенным OpenAPI-пайплайном. Менять работающее на слово «удобнее» не надо. Хотя бы потому, что миграция затронет и бэкенд-команду, и релизный процесс.
Практический шаг: возьмите один самый болезненный эндпоинт. Тот, который чаще ломался. Опишите тип запроса и ответа, прогоните через createValidators, посмотрите на схему глазами. Если схема совпала с ожиданиями и вылезли неочевидные поля — вы нашли деньги, которые теряли на отладке.
Где подвох
Инструмент молодой, и это меняет правила игры в оценке рисков. Бенчмарк на 2 мс — замер автора, независимой проверки нет. Охват 872 читателя на старте означает: широкой базы пользователей, которая выловит краевые случаи, пока нет.
Второй момент — парсер TS. Собственный syntax interpreter, который быстрее штатных, это одновременно преимущество и обязательство. Чем экзотичнее конструкция в ваших DTO (условные типы, mapped types, сложные дженерики), тем выше шанс попасть в getDegraded. Хорошо, что деградация видна и логируется. Плохо, что разбираться с ней вам, а не автору.
Третье: библиотека живёт внутри набора @arfa-tools/generator. Это не отдельный пакет с собственным жизненным циклом. Для небольшой команды нормально, для компании с требованиями к вендорской поддержке — повод подумать.
Вердикт
Это не замена OpenAPI-пайплайну в масштабе. Это замена ручной валидации на границе фронта и бэка там, где пайплайна нет и не будет. Разница принципиальная: вы не покупаете файл спецификации, вы достраиваете то, что уже написали руками в типах.
Экономика простая. Настройка — один вечер. Рантайм — меньше 1 КБ. Схема — обычный объект, из которого можно вырасти в любую сторону. Риск — молодость проекта и неполный разбор редких конструкций TS.
Брать или подождать: вердикт за 30 секунд
- Брать, если фронт и бэк разведены, контракты плывут, а спецификации либо нет, либо она врёт.
- Брать точечно, если хотите сначала проверить на одном эндпоинте без правок в архитектуре.
- Подождать, если у вас работает OpenAPI-пайплайн с налаженным релизным циклом и ломается он редко.
- Подождать, если в DTO тяжёлые типы — сначала прогоните их через генератор и посчитайте, сколько имён упадёт в getDegraded.
Вопросы, которые задают первыми
Нужно ли переписывать существующие типы?
Нет. Валидаторы строятся из тех DTO, что уже лежат в проекте. Правки нужны, только если типы описывают не то, что реально приходит.
Это работает только с React?
Привязки к фреймворку нет: модуль проверяет данные и чистит поля. Где вы делаете fetch, там и подключаете.
Схему можно отдать бэкенду?
Да, она сериализуется в JSON. Это удобный способ показать расхождение без спора о том, чья спецификация свежее.
А если библиотека перестанет развиваться?
У вас на руках JS-объекты со схемами. Их можно конвертировать в Zod или аналог и жить дальше. Привязки к рантайму почти нет — он весит меньше 1 КБ.
Что сделать на этой неделе
Откройте документацию (есть русская версия), возьмите один эндпоинт, который уже приносил проблемы. Прогоните через генератор, сравните полученную схему с тем, что вы думали об этом ответе раньше. Если расхождений ноль — валидация вам пока не нужна, и это хорошая новость. Если расхождения есть — вы только что нашли место, где приложение врёт пользователю молча.