- PVSM.RU - https://www.pvsm.ru -
Привет! Меня зовут Данил, я Frontend разработчик, живу в Питере, работаю в компании Unistory. Решил рассказать на Хабре, как автоматизировать один нудный процесс.
Зачастую нам приходится описывать запросы или переписывать уже имеющиеся ввиду изменения каких-то DTO (Data Transfer Object) или параметров у запросов. Это вполне естественно для разработки, но часто оказывается скучным и однотипным процессом, не требующим размышлений или особых навыков. Как следствие, его автоматизация кажется отличным выходом.
Автоматизация генерации API-контроллеров на клиенте возможна с помощью инструментов openapi-merge-cli и swagger-typescript-api. Они позволяют генерировать код на основе OpenAPI (Swagger) спецификации, минимизируя ручной труд и снижая вероятность ошибок.
Данное решение отлично подходит для быстрого старта в проектах уровня MVP (Minimum Viable Product) или PoC (Proof of Concept). Однако, при разработке масштабируемого продукта с гибкой архитектурой оно может накладывать определенные ограничения, и в таких случаях стоит рассмотреть более детальную кастомизацию API-клиента.
Установка зависимостей:
npm i --save-dev openapi-merge-cli swagger-typescript-api
Добавление скриптов:
"merge-openapi": "npx openapi-merge-cli --config ./swagger/openapi.config.json",
"codegen:api": "node codegen/codegen.js",
"codegen": "npm run merge-openapi && npm run codegen:api",
Структура проекта:
project-root
├──
api # Папка для сгенерированных файлов (создается вручную пустой!)
│ ├── Users.ts # Контроллер для работы с пользователями
│ ├── Orders.ts # Контроллер для работы с заказами
│ ├── http-client.ts # HTTP-клиент (обертка над axios/fetch)
│ ├── data-contracts.ts # Типы и DTO, сгенерированные из спецификации
│ ├── openapi.json # Объединённая OpenAPI спецификация после merge
│
├──
codegen # Папка с кодогенерацией API-клиента
│ ├── codegen.js # Скрипт генерации API-клиента на основе OpenAPI спецификации
│
├──
swagger # Папка с OpenAPI спецификациями
│ ├── openapi.config.json # Конфигурация объединения OpenAPI схем
│ ├── api.swagger.json # OpenAPI спецификация бэкенд-сервиса (может быть несколько)
│ ├── users.swagger.json # Спецификация для сервиса пользователей (если API разбито на части)
│ ├── orders.swagger.json # Спецификация для сервиса заказов (если API разбито на части)
│
├──
src # Папка с кодом проекта
│
├── package.json # Конфигурация проекта, включая npm-скрипты для генерации API
Что делает?
openapi-merge-cli объединяет несколько OpenAPI-спецификаций в один файл.
Зачем?
Если у тебя API разбито на несколько частей (**-**например, users.json, orders.json), этот шаг создает единый openapi.json, который потом передаётся в генератор.
Чтобы объединить несколько OpenAPI-файлов в один, используется конфигурационный файл swagger/openapi.config.json. Он указывает, какие файлы должны быть объединены и куда сохранить итоговый результат.
Пример openapi.config.json:
// /swagger/openapi.config.json
{
"inputs": [
{
"inputFile": "./users.swagger.json"
},
{
"inputFile": "./orders.swagger.json"
}
],
"output": "../api/openapi.json"
}
inputs – массив с путями к OpenAPI-файлам, которые нужно объединить. В этом примере соединяются users.swagger.json и orders.swagger.json.
output – путь, куда будет сохранён объединённый файл openapi.json.
Что делает?
swagger-typescript-api — это инструмент для генерации API-клиента и типов на основе OpenAPI-спецификации. Он анализирует openapi.json и создаёт готовый TypeScript-код для работы с API.
Основные возможности:
Генерация полностью типизированных API-запросов.
Автоматическое создание моделей (DTO) на основе OpenAPI-спеки.
Поддержка разных подходов: fetch, axios и других.
Гибкая конфигурация (например, можно менять структуру путей, типы, включать/выключать комментарии и т. д.).
Зачем?
Упрощает работу с API — тебе не нужно вручную писать интерфейсы и запросы.
Снижает вероятность ошибок, так как весь API-клиент типизирован.
Позволяет легко поддерживать актуальность API-кода при изменении бэкенда.
Файл codegen/codegen.js (который вызывается командой npm run codegen:api) нужно создать вручную, если его нет в проекте. В этом файле содержится конфигурация для кодгена swagger-typescript-api
// /codegen/codegen.js
import path from 'path'
import { generateApi } from 'swagger-typescript-api'
// Генерация API-клиента на основе OpenAPI-спецификации
generateApi({
// Имя выходного файла (если modular: false)
name: 'api.ts',
// Использовать единый HTTP-клиент (axios)
singleHttpClient: true,
// Выбор HTTP-клиента (axios или fetch)
httpClientType: 'axios',
// Разбить API-клиент на модули (файлы)
modular: true,
// Выносить тело запроса в отдельный параметр
extractRequestBody: true,
// Выносить query и path параметры в отдельный параметр
extractRequestParams: true,
// Выносить тело ответа в отдельный параметр
extractResponseBody: true,
// Выносить ошибки API в отдельный параметр
extractResponseError: true,
// Генерировать типы ответов API
generateResponses: true,
// Использовать объединённые enum-типы (`enum | string`)
generateUnionEnums: true,
// Исправлять ошибки OpenAPI-спеки (если есть несовместимости)
patch: true,
// Генерировать клиент для работы с API
generateClient: true,
// Индекс для именования модулей API
moduleNameIndex: 1,
// Путь к OpenAPI-файлу
input: path.resolve(process.cwd(), './api/openapi.json'),
// Папка для сохранения сгенерированного кода
output: path.resolve(process.cwd(), './api'),
})
Этот скрипт выполняет генерацию API-клиента на основе OpenAPI-спецификации. Он берет openapi.json, создаёт типизированные запросы и модели данных, а затем сохраняет их в ./api.
Получаем API спецификацию → На странице сваггера находим OpenAPI JSON-схему. Как правило, она находится под названием проекта.
Если схемы нет, бекендер должен её описать в коде, используя инструменты типа NestJS Swagger, FastAPI, SpringDoc или другие подходящие для его стека. Без этого генерация API-клиента невозможна.
Также в данной реализации важно понимать, что при обновлениях бекенда, необходимо каждый раз обновлять спеку на клиенте и выполнять команду npm run codegen, но если статья вам покажется интересной, я обязательно напишу продолжение, в котором реализую автоматическое подтягивание спеки.
Выгружаем JSON схему на клиент → Полученную спеку нужно сохранить в файл swagger/api.swagger.json , если таковых несколько, разбиваем их по отдельным файлам и указываем пути в swagger/openapi.config.json , как правило схема отражает сервис с которым вы работаете.
Пример с одним бекенд сервисом:
// /swagger/openapi.config.json
{
"inputs": [
{
"inputFile": "./api.swagger.json"
}
],
"output": "../api/openapi.json"
}
Пример с отдельными сервисами:
// /swagger/openapi.config.json
{
"inputs": [
{
"inputFile": "./identity.swagger.json"
},
{
"inputFile": "./payment.swagger.json"
},
{
"inputFile": "./users.swagger.json"
},
{
"inputFile": "./admin.swagger.json"
}
],
"output": "../api/openapi.json"
}
Запускаем скрипт слияния и генерации → Для удобства я создал скрипт, который объединяет две команды в одну: npm run codegen. В результате выполнения мы получаем папку api в корне проекта с автоматически сгенерированными типами и контроллерами для работы с API. Внутри папки будут следующие файлы:
http-client.ts — сгенерированный HTTP-клиент, обёртка над axios, содержащая базовую логику запросов, работу с заголовками, авторизацией и обработку параметров.
data-contracts.ts — автоматически сгенерированные TypeScript-типы, соответствующие моделям из OpenAPI. Используются для типизации данных в проекте.
openapi.json — объединённый файл спецификации OpenAPI, описывающий все доступные API-эндпоинты.
Auth.ts, Users.ts и другие контроллеры — автоматически сгенерированные файлы с методами для работы с API. Они формируются на основе эндпоинтов, описанных в Swagger (OpenAPI), и содержат готовые функции для выполнения запросов. Например, Auth.ts включает методы для авторизации (login, logout), а Users.ts — для работы с пользователями (getUser, updateUser).
Чтобы использовать контроллеры, необходимо создать экземпляр HttpClient и передать его в контроллеры, обеспечивая единую точку конфигурации для запросов в проекте.
Объявляем HTTP Client → Создаем экземпляр HttpClient из сгенерированного кода, указывая базовый URL и необходимые параметры — фактически это аналог инстанса axios для работы с API. Файл с клиентом (http-client.ts) может располагаться в разных местах в зависимости от архитектурной методологии проекта (например, в папке services, lib или api). В моем случае это FSD (Feature-Sliced Design), где такой файл обычно находится в папке shared/api или shared/lib
// /src/shared/api/http-client.ts
import { HttpClient } from '@api/http-client'
import { Constants } from '@shared/lib'
export const httpClient = new HttpClient({
baseURL: Constants.API_URL,
// Другие параметры по необходимости
})
Объявляем API-контроллеры → Создаём экземпляры сгенерированных контроллеров. В зависимости от архитектурного подхода их можно инициализировать прямо в http-client.ts для простого варианта или разделить код на модули, объявляя контроллеры внутри соответствующих модулей. Такой подход позволяет лучше структурировать код и упрощает масштабирование проекта.
Пример базовый:
// /src/shared/api/http-client.ts
import { Auth } from '@api/Auth'
import { Invoices } from '@api/Invoices'
import { OAuthAccount } from '@api/OAuthAccount'
import { Payments } from '@api/Payments'
import { Subscriptions } from '@api/Subscriptions'
import { Users } from '@api/Users'
import { UserSubscriptions } from '@api/UserSubscriptions'
import { HttpClient } from '@api/http-client'
import { Constants } from '@shared/lib'
export const httpClient = new HttpClient({
baseURL: Constants.API_URL,
// Другие параметры по необходимости
})
// Auth controllers
export const authController = new Auth(httpClient)
export const authIntegrationsController = new OAuthAccount(httpClient)
// Subscriptions controllers
export const invoicesController = new Invoices(httpClient)
export const paymentsController = new Payments(httpClient)
export const subscriptionsController = new Subscriptions(httpClient)
export const userSubscriptionsController = new UserSubscriptions(httpClient)
// Users controllers
export const usersController = new Users(httpClient)
Пример с сущностями / модулями:
// /src/entites/user/api/users-controller.ts
import { httpClient } from '@shared/api'
import { Users } from '@api/Users'
export const usersController = new Users(httpClient)
// /src/entites/auth/api/auth-controller.ts
import { httpClient } from '@shared/api'
import { OAuthAccount } from '@api/OAuthAccount'
import { Auth } from '@api/Auth'
export const authController = new Auth(httpClient)
export const authIntegrationsController = new OAuthAccount(httpClient)
Представим, что наш сваггер выглядел так:
После всех вышеперечисленных действий в папке api мы получим такую структуру:
project-root
├──
api # Папка для сгенерированных файлов (создается вручную пустой!)
│ ├── Admins.ts # Контроллер для работы с администраторами
│ ├── Auth.ts # Контроллер для работы с аутентификацией
│ ├── CounterNftLotOffers.ts # Контроллер для управления ставками на NFT-лоты
│ ├── Lots.ts # Контроллер для работы с лотами
│ ├── Nft.ts # Контроллер для работы с NFT
│ ├── NftLots.ts # Контроллер для работы с коллекциями NFT
│ ├── Notifications.ts # Контроллер для работы с уведомлениями
│ ├── PrivateOrders.ts # Контроллер для частных заказов
│ ├── ScDeals.ts # Контроллер для сделок
│ ├── ScDealsCharts.ts # Контроллер для графиков по сделкам
│ ├── SeasonRewardHistories.ts # Контроллер для истории сезонных наград
│ ├── SeasonUsers.ts # Контроллер для сезонных пользователей
│ ├── Seasons.ts # Контроллер для сезонов
│ ├── Users.ts # Контроллер для пользователей
│ ├── http-client.ts # HTTP-клиент (обертка над fetch/axios)
│ ├── data-contracts.ts # Типы и DTO, сгенерированные из OpenAPI спецификации
│ ├── openapi.json # Объединённая OpenAPI спецификация после merge
│
├──
codegen # Папка с кодогенерацией API-клиента
│ ├── codegen.ts # Скрипт генерации API-клиента на основе OpenAPI спецификации
│
├──
swagger # Папка с OpenAPI спецификациями
│ ├── openapi.config.json # Конфигурация объединения OpenAPI схем
│ ├── nft-market.swagger.json # OpenAPI спецификация NFT-маркета
│
├──
src # Папка с кодом проекта
│
├── package.json # Конфигурация проекта, включая npm-скрипты для генерации API
├── tsconfig.json # Конфигурация TypeScript (если используется TS)
Admins.ts, Auth.ts, CounterNftLotOffers.ts, ..., Users.ts
Каждый файл представляет API-контроллер для соответствующей сущности.
Содержит классы и методы для запросов (GET, POST, PUT, DELETE).
Использует HTTP-клиент для выполнения запросов.
data-contracts.ts
Описывает типы данных (интерфейсы) для запросов и ответов API.
Используется во всех контроллерах.
http-client.ts
Отвечает за отправку HTTP-запросов.
Может содержать кастомную логику для работы с токенами и авторизацией.
openapi.json
Исходный OpenAPI (Swagger) JSON, на основе которого была сгенерирована API-структура.
Не изменяйте вручную файлы контроллеров и типов — после обновления API и повторной кодогенерации ваши правки будут перезаписаны.
Если необходимо изменить типы, делайте это на уровне вашего приложения, расширяя или объединяя их, вместо редактирования сгенерированных файлов.
Если вы выполнили все описанные выше шаги, у вас уже есть готовые экземпляры API-контроллеров.
// /src/shared/api/http-client.ts
import { Auth } from '@api/Auth'
import { HttpClient } from '@api/http-client'
import { Constants } from '@shared/lib'
export const httpClient = new HttpClient({
baseURL: Constants.API_URL,
// Другие параметры по необходимости
})
// Auth controllers
export const authController = new Auth(httpClient)
Дальнейшее взаимодействие зависит от вашего подхода к работе с запросами и используемого фреймворка или библиотеки. В React это может быть react-query, rtk-query и другие. В следующей статье я подробнее разберу, как интегрировать кодген с кешированием через react-query.
Посмотрим на практический пример работы с сгенерированными контроллерами.
Видео версия:
Если вы не хотите импортировать типы и контроллеры из внешней папки, вы можете объявлять типы на уровне сущности или модуля, используя реэкспорт или расширение/объединение типов.
Вот так я автоматизирую генерацию API-контроллеров на проектах Unistory. Планирую и дальше публиковать на Хабре такие гайды. Надеюсь, эта статья была полезной ;-)
Автор: codeoverdose
Источник [1]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/avtomatizatsiya/414243
Ссылки в тексте:
[1] Источник: https://habr.com/ru/articles/893150/?utm_source=habrahabr&utm_medium=rss&utm_campaign=893150
Нажмите здесь для печати.