Сервис развивается: тестируем формат, собираем идеи, улучшаем сервис. Есть идеи?

Написать
Войти
Дайджесты новостей
Иллюстрация к статье об автогенерации типов OpenAPI между фронтендом и бэкендом

Автогенерация типов из OpenAPI: как связать Laravel и TypeScript, перестроить CI/CD и сэкономить 40% трудозатрат

Сквозная автогенерация TypeScript-типов из OpenAPI на базе Laravel и Scramble помогла команде вскрыть скрытые разногласия в именовании сущностей, перейти к монорепозиторию, стабилизировать сборку и снизить трудозатраты на оценку спринтовых задач на 20–40% без постоянных поломок dev-окружения.

Автогенерация типов из OpenAPI: как связать Laravel и TypeScript, перестроить CI/CD и сэкономить 40% трудозатрат

В современной веб-разработке рассинхронизация контрактов между бэкендом и фронтендом остается главным источником скрытых ошибок и взаимного недовольства команд. Классический сценарий знаком каждому: бэкендер переименовал поле в ответе или изменил его тип, забыл предупредить коллег, выкатил изменения в dev-окружение — и у фронтендера перестает работать интерфейс. Попытки решить проблему ручным написанием TypeScript-интерфейсов лишь создают иллюзию контроля: дублирование схем отнимает до трети рабочего времени и неизбежно устаревает.

Решением проблемы часто видится внедрение автогенерации типов на основе спецификации OpenAPI. Однако на практике технический инструмент моментально вытаскивает на поверхность глубокие организационные противоречия. Реальный инженерный опыт команды, внедрившей сквозную кодогенерацию в стеке Laravel и TypeScript, наглядно показывает: автоматизация работает только тогда, когда за ней стоит перестройка процессов, единый язык и дисциплина передачи задач.

Источник правды: почему Swagger и Postman проигрывают коду

Центральный вопрос архитектуры контрактов — где находится единый источник правды (Single Source of Truth). Ручное ведение спецификаций Swagger или коллекций Postman в отдельных файлах быстро превращается в обузу: разработчики откладывают актуализацию документации на последний момент, и схемы расходятся с реальным поведением API.

В экосистеме PHP и фреймворка Laravel (начиная с версии 10 и PHP 8.1+) альтернативой ручному труду стала библиотека Scramble от Dedoc. В отличие от Python FastAPI со встроенным Pydantic, где типы выводятся из рефлексии времени выполнения, в Laravel генератор анализирует код контроллеров, классы Resource и аннотации PHPDoc, автоматически формируя спецификацию OpenAPI 3.1.0 непосредственно из кодовой базы.

[Laravel Backend: Controllers, Resources, PHPDoc] ──> [Scramble: php artisan scramble:export] ──> [api.json (OpenAPI 3.1)]
                                                                                                        │
[React Frontend: UI Models & Adapters] <── [Generated Client: src/generated/api] <── [openapi-generator-cli]

Техническая цепочка интеграции строится на официальных открытых инструментах:

1. Настройка генерации на стороне Laravel

Пакет устанавливается в проект через Composer:

composer require dedoc/scramble

После установки в локальном окружении автоматически становятся доступны интерактивный UI документации по адресу /docs/api и машиночитаемая спецификация /docs/api.json. Для тонкой настройки маршрутов и путей экспорта конфигурация публикуется в проект:

php artisan vendor:publish --provider="Dedoc\Scramble\ScrambleServiceProvider" --tag="scramble-config"

Команда экспорта позволяет сохранить спецификацию в статический файл для пайплайна сборки:

php artisan scramble:export --path=api.json
2. Генерация клиентских типов TypeScript

На стороне фронтенда официальный CLI-генератор OpenAPI Generator преобразует полученный api.json в строго типизированный транспортный слой:

openapi-generator-cli generate \
  -i api.json \
  -g typescript-fetch \
  -o src/generated/api \
  -c openapi-generator.config.yaml

Сгенерированные файлы не коммитятся в Git: они считаются производными артефактами сборки и создаются заново при локальном старте и в CI/CD. Это исключает конфликты слияния и гарантирует 100% соответствие кода актуальной схеме.

Процессный барьер: семантический переводчик и единый язык

Как только генератор запустили впервые, выяснилось: главная проблема лежит не в синтаксисе, а в терминологии. Бэкенд и фронтенд месяцами называли одни и те же бизнес-сущности разными именами. На бэкенде фигурировала таблица offerings, а в интерфейсе фронтендеры оперировали понятием services. В коде возник скрытый слой ментального перевода, порождавший путаницу на каждом созвоне.

Автогенерация типов сделала эти расхождения очевидными и болезненными: автосгенерированный интерфейс OfferingDto ломал весь фронтенд, где ожидали Service. Команде пришлось внедрить концепцию предметно-ориентированного проектирования (DDD) — единый язык (Ubiquitous Language). Названия моделей, эндпоинтов и полей стали согласовывать до написания первой строчки кода на этапе планирования спринта.

Архитектурные правила: изоляция DTO и отказ от рантайм-мапперов

Чтобы сквозная типизация не превратила кодовую базу в хрупкий монолит, команда выработала три жестких правила:

  1. Запрет наследования и прямого использования DTO в UI: Сгенерированные транспортные объекты (Data Transfer Objects, DTO) строго изолируются на границе API-слоя. Компоненты интерфейса не принимают DTO напрямую в props. Для интерфейса создаются собственные модели, содержащие только необходимые UI-поля, а преобразование выполняется явными чистыми функциями-адаптерами. Это защищает дизайн от поломок при изменении структуры серверного ответа.
  2. Фиксация camelCase в контрактах бэкенда: Разница соглашений об именовании (snake_case в PHP/SQL против camelCase в JavaScript) традиционно решается рантайм-мапперами или сложными TypeScript-утилитами. Команда отказалась от лишних накладных расходов: в полях Resource и DTO бэкенда зафиксировали нотацию camelCase для всех ответов API, сохранив snake_case только внутри таблиц БД.
  3. Запрет ручных правок в сгенерированных файлах: Если сгенерированный тип кажется неудобным или неполным, исправления вносятся исключительно в исходный код контроллера или аннотацию PHPDoc на бэкенде.

Переход к монорепозиторию и новый пайплайн CI/CD

Попытка использовать автогенерацию в двух раздельных репозиториях привела к параличу: бэкендер обновлял схему, пушил в свой dev, а фронтенд в отдельной ветке падал с ошибками типизации. Чтобы разблокировать работу, бэкенд и фронтенд объединили в единый монорепозиторий.

Фронтенд-разработчики получили возможность запускать бэкенд локально в Docker с предзаполненным дампом тестовых данных. Процесс передачи задачи в спринте кардинально изменился:

КритерийДо внедрения (раздельные репозитории)После внедрения (монорепозиторий и Scramble)
Синхронизация контрактаНеформальные договоренности в чатах, отстающий PostmanСпецификация OpenAPI 3.1, экспортируемая из кода
Разработка фичиБэкенд пушит сырой API в shared dev, блокируя коллегРазработка в единой ветке, локальный запуск в Docker
Обнаружение ошибокНа этапе ручного QA или в продакшенеНа этапе компиляции TypeScript и в CI-пайплайне
Рутина типизацииРучное дублирование типов интерфейсов (до 30% времени)Автогенерация типов одной командой CLI
Выкатка в devЧастые поломки общего окруженияТолько проверенный и протестированный монолитный срез

Алгоритм работы над задачей в монорепозитории:

[1. Согласование контракта до спринта]
                  │
                  ▼
[2. Бэкендер реализует логику и PHPDoc в ветке task/feature] ──> [Проверка diff спецификации api.json]
                  │
                  ▼
[3. Передача ветки фронтендеру] ──> [Локальный запуск `npm run generate-api`] ──> [Реализация UI через адаптеры]
                  │
                  ▼
[4. Сборка, TypeCheck и тесты в CI/CD] ──> [Единый Merge Request в dev-ветку]

Экономика и ограничения: когда подход окупается

Спустя полгода промышленной эксплуатации команда сопоставила фактические трудозатраты со старыми проектами аналогичной сложности. В условиях двухнедельных спринтов аутсорс-разработки были зафиксированы следующие результаты:

  • Сокращение рутины: экономия до 30% времени фронтендеров на написании типовых интерфейсов;
  • Снижение смет на спринт: сокращение трудозатрат на разработку задач на 20–40% за счет исключения переделок и поиска багов рассинхронизации;
  • Экономия бюджета: уменьшение первичной сметы нового проекта на 200–300 тысяч рублей благодаря ускорению базовой интеграции.

Однако подход имеет очевидную цену:

  • Время код-ревью бэкенда выросло, так как ревьюеры обязаны проверять точность PHPDoc-аннотаций и diff в api.json;
  • Онбординг новых разработчиков требует дополнительного времени на освоение Docker-окружения и генератора;
  • Схема нецелесообразна для монолитных стеков с Inertia.js, где данные передаются напрямую в представления, а также для команд с внешним бэкендом, над которым нет прямого контроля.

Автогенерация типов из OpenAPI — это не волшебная кнопка, а зеркало инженерной культуры. Она устраняет техническую рутину, но взамен требует прозрачных договоренностей, строгости в коде и готовности слышать коллег.