Карта дня, май, перетяжка
Карта дня, май, перетяжка
Карта дня, май, перетяжка

Как упростить работу с API в React-приложении с помощью RTK Query и OpenAPI?

Аватарка пользователя Дима Державин
для
Логотип компании Tproger
Tproger
Отредактировано

Разбираемся, как автоматически генерировать код для работы с API в React с помощью RTK Query и OpenAPI, чтобы писать меньше ручного кода и ускорить разработку.

624 открытий3К показов
Как упростить работу с API в React-приложении с помощью RTK Query и OpenAPI?
Используете кодген в своих проектах?
Да
Нет

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

В статье мы разберёмся, как запустить кодген на основе OpenApi схемы для веб-приложения на стэке React + redux-toolkit.

Для справки: контракт — соглашение о формате данных между клиентом и сервером

Какие проблемы решает кодген?

Любой разработчик может совершить ошибку: неудачно разрезолвить мердж-конфликт, забыть предупредить о рефакторинге или банально опечататься.

В микро-сервисной архитектуре цена таких ошибок высока — оперативно доставить обновления многочисленным клиентам иногда бывает проблематично.

Кодген снижает вероятность таких ошибок, так как методы статично создаются на основе схемы, которая, в идеале, также должна генерироваться на основе кода бекенда. Рассмотрим подробнее.

Синхронизация контрактов

При активной разработке бекенд постоянно обновляет контракты. Без кодгена все обновления на стороне фронтенда синхронизируются вручную.

Также на основе контрактов создаются побочные интерфейсы, например, для redux-слайсов и react-компонентов. Ошибка в основном контракте создает цепочку проблем со связанными типами и интерфейсами.

Сочетание кодген-контрактов и typescript-утилит для создания побочных интерфейсов делает доставку обновлений простой и безопасной.

Устранение дубликатов

Когда проект не имеет четкой структуры, контракты оказываются разбросаны по всей кодовой базе. Разработчики, не сумев найти нужный интерфейс, просто создают свои варианты контрактов. Так рождаются дубликаты и несогласованность.

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

Совет: Кодген проще и безопасней внедрять на начальных этапах разработки. А в уже написанных проектах — стоит внедрять его постепенно, например, по несколько эндпойнтов за раз.

Что понадобиться для кодгена?

Прежде чем запускать кодген нам нужно минимально настроить проект:

Подготовить схему

OpenAPI-схема в формате yaml:

			openapi: 3.0.3
info:
  title: Users API
  version: 1.0.0
servers:
  - url: https://api.example.com/v1
paths:
  /users/{id}:
    get:
      tags:
        - Users
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
            format: int64
      responses:
        200:
          description: Успешный ответ
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
        500:
          description: Ошибка сервера
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
          format: int64
        username:
          type: string
        email:
          type: string
          format: email
        createdAt:
          type: string
          format: date-time
      required:
        - id
        - username
        - email
    Error:
      type: object
      properties:
        message:
          type: string
          example: "User not found"
        code:
          type: integer
          example: 404
      required:
        - message
        - code
		

Важно: Любая ошибка в схеме приводит к поломке кодгена. Поэтому важно, чтобы ваша схема проходила валидацию.

Проверить валидность OpenAPI схемы можно на в swagger редакторе

Настроить Redux

Redux-клиент для API-запросов:

			/** api.ts */

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

export const baseApi = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: "/api" }),
  endpoints: () => ({}),
});

		

Redux-слайс для хранения данных из API-запроса:

			/** user.ts */

import { createSlice } from "@reduxjs/toolkit";
import { codegenApi, User } from "./codegen/codegenApi";

const initialState: User | null = null;

export const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    getUserDetails: (user) => user,
  },
  extraReducers: (builder) => {
    builder.addMatcher(
      codegenApi.endpoints.getUsersById.matchFulfilled,
      (state: User | null, response) => {
        state = response.payload;
      }
    );
  },
});

export const { getUserDetails } = userSlice.actions;

		

Redux-хранилище с подключенным API-клиентом и редюсером:

			/** store.ts */

import { configureStore } from "@reduxjs/toolkit";

import { userSlice } from "./user";
import { baseApi } from "./api";

export const store = configureStore({
  reducer: {
    [userSlice.name]: userSlice.reducer,
    [baseApi.reducerPath]: baseApi.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(baseApi.middleware),
});
		

Настроить кодген

Для кодгена возьмём официальную библиотеку от Redux — @rtk-query/codegen-openapi

Затем создадим файл настройками кодгена:

			/** codegen.ts */    

import  type { ConfigFile } from "@rtk-query/codegen-openapi";

const config: ConfigFile = {

  /** Обязательные параметры */

  schemaFile: "./openapi.yaml",    // Путь к схеме
  apiFile: "./src/redux/api.ts",   // Путь к API-клиенту
  apiImport: "baseApi",            // Имя переменной API-клиента в api.ts
  exportName: "codegenApi",        // Имя переменной API-клиент ав codegenApi.ts
  outputFile: "./src/redux/codegen/codegenApi.ts", // Результат кодгена
  
  /** Дополнительные параметры */
  
  argSuffix: "Props",          // Суффикс интерфейса аргументов запроса
  responseSuffix: "Response",  // Суффикс интерфейса ответа сервера
  hooks: {                     // Генерировать RTK-query хуки
    queries: true,             // Хуки для GET запросов
    lazyQueries: true,         // Ленивые хуки (useLazyQuery)
    mutations: true,           // Хуки для мутаций
  },
  tag: false,                  // Группировать эндпоинты по тегам
  flattenArg: false,           // Разворачивать сложные параметры запросов
  useEnumType: false,          // Использовать enum вместо union типов
  filterEndpoints: [],         // Фильтрация эндпоинтов для генерации
  endpointOverrides: [],       // Переопределение параметров эндпоинтов
};

export default config;

		

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

Если вам нужна гибкость, попробуйте использовать @openapitools/openapi-generator-cli с различными готовыми шаблонами или Orval от tanstack

Запуск кодгена

Для запуска добавим команду в package.json:

			  "scripts": {
    "codegen": "rtk-query-codegen-openapi codegen.ts"
  },
		

Далее заходим в корень проекта и запускаем команду:

			npm run codegen
		

На выходе должен получиться файл codegenApi.ts с содержимым:

			import { baseApi as api } from "../api";

const injectedRtkApi = api.injectEndpoints({
  endpoints: (build) => ({
    getUsersById: build.query<GetUsersByIdResponse, GetUsersByIdProps>({
      query: (queryArg) => ({ url: `/users/${queryArg.id}` }),
    }),
  }),
  overrideExisting: false,
});

export { injectedRtkApi as codegenApi };

export type GetUsersByIdResponse = /** status 200 Успешный ответ */ User;

export type GetUsersByIdProps = {
  id: number;
};

export type User = {
  id: number;
  username: string;
  email: string;
  createdAt?: string;
};

export type Error = {
  message: string;
  code: number;
};

export const { useGetUsersByIdQuery, useLazyGetUsersByIdQuery } =
  injectedRtkApi;

		

Проверяем результат:

  • Инджект кодген эндпойтов в baseApi;
  • Системные типы Props Response Error;
  • Контракт User;
  • RTK-query хуки.
Совет: создание кодгена можно автоматизировать, например, сделать его отдельным этапом в CI/CD, который выполняется прямо перед билдом приложения.

Возможные проблемы

Необычные типы данных

При внедрении кодгена в проект на Kotlin я столкнулся с проблемой парсинга: кодген не мог распарсить тип данных LocalDateTime, который должен возвращать строку с датой в формате ISO. Вместо строки когден возвращал пустой объект.

LocalDateTime — это класс из Java-библиотеки java.time, который представляет дату и время без учёта временной зоны.

Данную проблему я решил с помощью кодмода:

			module.exports = function (fileInfo, api) {
 const j = api.jscodeshift;
 const root = j(fileInfo.source);


 root
   .find(j.ExportNamedDeclaration, {
     declaration: {
       type: "TSTypeAliasDeclaration",
       id: {
         name: "LocalDateTime",
       },
       typeAnnotation: {
         type: "TSTypeLiteral",
         members: [],
       },
     },
   })
   .replaceWith(() => {
     const newType = j.tsTypeAliasDeclaration(
       j.identifier("LocalDateTime"),
       j.tsStringKeyword(),
     );


     return j.exportNamedDeclaration(newType);
   });


 return root.toSource({
   quote: "single",
   trailingComma: true,
   lineTerminator: "\n",
 });
};

"codegen:codemod": "jscodeshift -t ./codemod.js ./src/redux/codegen/codegenApi.ts --parser=tsx",

		

Кодмод запускается после команды codegen. Он бежит по файлу сверху вниз, находит нужный тип и меняет его значение на string.

Совет: При добавлении кодмода в общий файл обязательно опишите проблему которую он решает. Когда кодмодов станет много и они начнут конфликтовать, вам будет проще разобраться в их работе.

Кодмоды отлично генерируются с помощью AI.

Плюсы и минусы кодгена

Плюсы:

  • Генерируем API-слой всего приложения за секунды;
  • Получаем автоматическое строгое создание интерфейсов и типов;
  • Избавляемся от опечаток в URL и параметрах;
  • Обнаруживаем проблемы с обратной совместимостью контрактов на ранних этапах сборки;
  • Экономим время.

Минусы:

  • Качество кодгена зависит от качества описания OpenAPI схемы;
  • Если схема не генерируется в автоматическом режиме, актуальности OpenAPI схемы потребуется поддерживать вручную;
  • Логику нестандартных запросов придётся описывать вручную.
Напоминание: API-слой сложного проекта невозможно покрыть кодогенерацией на 100% — это нормально. Будьте готовы при помощи методов enhanceEndpoints и injectEndpoints комбинировать когден-эндпойнты с эндпойнтам написанными вручную 

Заключение

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

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

Ускоряй работу, автоматизируй рутину, используй лучшие тулзы. Все самые полезные инструменты тут.

Следите за новыми постами
Следите за новыми постами по любимым темам
624 открытий3К показов