Настройка генератора

Генератор RTK Query предназначен для автоматического создания API-клиентов, endpoints, hooks и типов на основе внешних схем API. Основная задача генератора — устранение ручного написания однотипного кода и синхронизация frontend-слоя с серверным контрактом.

Наиболее распространённые источники генерации:

  • OpenAPI (Swagger)
  • GraphQL Schema
  • custom schema generators
  • внутренние корпоративные API-описания

Генерация особенно важна в крупных проектах, где:

  • API содержит сотни endpoints;
  • backend развивается независимо;
  • требуется строгая типизация;
  • необходимо минимизировать ошибки ручного описания запросов.

Пакет codegen-openapi

Для генерации RTK Query API из OpenAPI используется пакет:

npm install @rtk-query/codegen-openapi

или:

yarn add @rtk-query/codegen-openapi

Пакет анализирует OpenAPI-схему и генерирует полноценный API slice.


Базовая структура проекта

Пример структуры:

src/
├── app/
│   └── store.ts
├── services/
│   ├── emptyApi.ts
│   ├── generatedApi.ts
│   └── openapi-config.ts
└── types/

Создание базового emptyApi

Генератор расширяет существующий API через injectEndpoints, поэтому сначала создаётся базовый API.

// services/emptyApi.ts

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

export const emptyApi = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({
    baseUrl: '/api',
  }),
  endpoints: () => ({}),
})

Такой подход особенно удобен при:

  • code splitting;
  • modular architecture;
  • разделении generated и handwritten endpoints;
  • SSR;
  • lazy loading.

Создание конфигурации генератора

Создаётся отдельный конфигурационный файл.

// services/openapi-config.ts

import { defineConfig } from '@rtk-query/codegen-openapi'

export default defineConfig({
  schemaFile: 'https://example.com/openapi.json',

  apiFile: './src/services/emptyApi.ts',

  apiImport: 'emptyApi',

  outputFile: './src/services/generatedApi.ts',

  exportName: 'generatedApi',

  hooks: true,
})

Разбор параметров конфигурации

schemaFile

Путь до OpenAPI-схемы.

Поддерживаются:

schemaFile: './openapi.json'
schemaFile: 'https://example.com/openapi.json'
schemaFile: './swagger.yaml'

apiFile

Файл с базовым API.

apiFile: './src/services/emptyApi.ts'

apiImport

Имя экспортируемого API.

apiImport: 'emptyApi'

Генератор импортирует именно этот объект:

import { emptyApi } from './emptyApi'

outputFile

Файл, в который будет записан результат генерации.

outputFile: './src/services/generatedApi.ts'

exportName

Имя экспортируемого generated API.

exportName: 'generatedApi'

Результат:

export const generatedApi = emptyApi.injectEndpoints(...)

hooks

Автоматическая генерация React hooks.

hooks: true

После генерации появляются:

useGetUsersQuery
useCreateUserMutation
useUpdatePostMutation

Запуск генерации

Через CLI

npx @rtk-query/codegen-openapi ./src/services/openapi-config.ts

Через package.json

{
  "scripts": {
    "generate-api": "rtk-query-codegen-openapi ./src/services/openapi-config.ts"
  }
}

Запуск:

npm run generate-api

Результат генерации

Пример generatedApi.ts:

import { emptyApi as api } from './emptyApi'

export const generatedApi = api.injectEndpoints({
  endpoints: (build) => ({
    getUsers: build.query<GetUsersApiResponse, GetUsersApiArg>({
      query: () => ({ url: `/users` }),
    }),

    createUser: build.mutation<
      CreateUserApiResponse,
      CreateUserApiArg
    >({
      query: (queryArg) => ({
        url: `/users`,
        method: 'POST',
        body: queryArg.body,
      }),
    }),
  }),
})

export const {
  useGetUsersQuery,
  useCreateUserMutation,
} = generatedApi

Подключение generated API к store

// app/store.ts

import { configureStore } from '@reduxjs/toolkit'
import { generatedApi } from '../services/generatedApi'

export const store = configureStore({
  reducer: {
    [generatedApi.reducerPath]: generatedApi.reducer,
  },

  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(
      generatedApi.middleware
    ),
})

Использование generated hooks

const UsersPage = () => {
  const { data, isLoading } =
    useGetUsersQuery()

  if (isLoading) {
    return <div>Loading...</div>
  }

  return (
    <div>
      {data?.map((user) => (
        <div key={user.id}>
          {user.name}
        </div>
      ))}
    </div>
  )
}

Генерация типов

Одно из важнейших преимуществ генератора — автоматическое создание типов.

Пример:

export type UserDto = {
  id: number
  name: string
  email: string
}

Типы генерируются на основе OpenAPI schema definitions.


Автоматическая типизация query arguments

OpenAPI:

/users/{id}:
  get:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer

Сгенерированный код:

getUser: build.query<
  GetUserApiResponse,
  GetUserApiArg
>

Тип аргумента:

export type GetUserApiArg = {
  id: number
}

Использование:

useGetUserQuery({
  id: 10,
})

TypeScript немедленно покажет ошибку при неправильных параметрах.


Генерация mutation endpoints

POST:

/users:
  post:
    requestBody:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CreateUserDto'

Результат:

createUser: build.mutation<
  CreateUserApiResponse,
  CreateUserApiArg
>

Mutation hook:

const [createUser, result] =
  useCreateUserMutation()

Вызов:

await createUser({
  body: {
    name: 'Alex',
    email: 'alex@test.com',
  },
})

transformResponse после генерации

Сгенерированные endpoints можно расширять вручную.


enhanceEndpoints

export const extendedApi =
  generatedApi.enhanceEndpoints({
    endpoints: {
      getUsers: {
        transformResponse: (response) => {
          return response.items
        },
      },
    },
  })

Добавление tagTypes

Часто generated API дополняется кеш-инвалидацией.

export const emptyApi = createApi({
  reducerPath: 'api',

  baseQuery: fetchBaseQuery({
    baseUrl: '/api',
  }),

  tagTypes: ['Users', 'Posts'],

  endpoints: () => ({}),
})

enhanceEndpoints для providesTags

export const enhancedApi =
  generatedApi.enhanceEndpoints({
    endpoints: {
      getUsers: {
        providesTags: ['Users'],
      },

      createUser: {
        invalidatesTags: ['Users'],
      },
    },
  })

Генерация без hooks

Иногда hooks не нужны.

Например:

  • SSR;
  • Node.js;
  • non-React environment;
  • custom wrappers.
hooks: false

Разделение generated и custom logic

Хорошей практикой считается разделение:

  • generated code;
  • бизнес-логики;
  • кастомных transformResponse;
  • tag invalidation;
  • optimistic updates.

generatedApi.ts

Только автогенерация.


extendedApi.ts

import { generatedApi } from './generatedApi'

export const extendedApi =
  generatedApi.enhanceEndpoints({
    endpoints: {
      getUsers: {
        providesTags: ['Users'],
      },
    },
  })

Почему generated файлы нельзя редактировать вручную

При следующем запуске генератора изменения будут потеряны.

Обычно generated файлы:

  • помечаются комментариями;
  • исключаются из ручного редактирования;
  • обновляются автоматически в CI/CD.

Автоматическая генерация в CI/CD

Пример:

- name: Generate RTK Query API
  run: npm run generate-api

Это гарантирует:

  • актуальность типов;
  • синхронность frontend/backend;
  • отсутствие устаревших endpoints.

Генерация нескольких API

В больших системах может существовать несколько backend-сервисов.

export default defineConfig({
  schemaFile: './billing-openapi.json',

  apiFile: './src/services/emptyApi.ts',

  apiImport: 'emptyApi',

  outputFile:
    './src/services/billingApi.ts',

  exportName: 'billingApi',
})

Второй конфиг:

export default defineConfig({
  schemaFile: './users-openapi.json',

  apiFile: './src/services/emptyApi.ts',

  apiImport: 'emptyApi',

  outputFile:
    './src/services/usersApi.ts',

  exportName: 'usersApi',
})

Генерация через YAML

Поддерживаются YAML-схемы:

schemaFile: './swagger.yaml'

Это особенно распространено в:

  • NestJS;
  • Spring;
  • Laravel;
  • FastAPI;
  • ASP.NET.

Переопределение baseQuery

Иногда generated API должен использовать кастомный baseQuery.

const customBaseQuery = fetchBaseQuery({
  baseUrl: '/api',

  prepareHeaders: (headers, api) => {
    const token =
      api.getState().auth.token

    if (token) {
      headers.set(
        'authorization',
        `Bearer ${token}`
      )
    }

    return headers
  },
})

Генерация API с авторизацией

Все generated endpoints автоматически используют базовый baseQuery.

export const emptyApi = createApi({
  reducerPath: 'api',

  baseQuery: customBaseQuery,

  endpoints: () => ({}),
})

Работа с enum

OpenAPI enum:

status:
  type: string
  enum:
    - active
    - blocked

Сгенерированный тип:

export type Status =
  | 'active'
  | 'blocked'

nullable-поля

OpenAPI:

email:
  type: string
  nullable: true

Результат:

email?: string | null

Генерация query params

OpenAPI:

parameters:
  - in: query
    name: page
    schema:
      type: integer

Результат:

useGetUsersQuery({
  page: 1,
})

Generated endpoint:

query: (queryArg) => ({
  url: `/users`,
  params: {
    page: queryArg.page,
  },
})

Генерация path params

/users/{id}

Результат:

query: (queryArg) => ({
  url: `/users/${queryArg.id}`,
})

Генерация request body

requestBody:
  content:
    application/json:
      schema:
        $ref: '#/components/schemas/UserDto'

Результат:

body: queryArg.userDto

Генерация response types

responses:
  '200':
    content:
      application/json:
        schema:
          $ref: '#/components/schemas/UserDto'

Результат:

build.query<UserDto, ...>

Частые проблемы генерации

Некорректная OpenAPI schema

Наиболее распространённая проблема.

Ошибки возникают при:

  • invalid refs;
  • циклических ссылках;
  • неправильных nullable;
  • отсутствии schema definitions.

Несовместимые OpenAPI версии

Наиболее стабильна поддержка:

  • OpenAPI 3.x

Swagger 2.0 иногда требует преобразования.


Огромные generated файлы

Крупные API могут генерировать тысячи строк кода.

Распространённые решения:

  • разделение схем;
  • code splitting;
  • modular APIs;
  • multiple generated slices.

Генерация только endpoints

Некоторые команды используют generator исключительно для:

  • DTO;
  • hooks;
  • endpoints;
  • type contracts.

Бизнес-логика остаётся ручной.


Интеграция с monorepo

Типичный сценарий:

packages/
├── backend/
├── frontend/
└── shared/

OpenAPI schema публикуется backend-пакетом.

Frontend генерирует RTK Query API автоматически.


Автоматическое обновление схемы

Пример:

curl https://example.com/openapi.json \
  -o ./openapi.json

После чего запускается:

npm run generate-api

Генерация во время разработки

Иногда используется watch-mode через nodemon:

{
  "scripts": {
    "generate:watch":
      "nodemon --watch openapi.json --exec npm run generate-api"
  }
}

Архитектурные рекомендации

Наиболее устойчивой считается следующая схема:

emptyApi
    ↓
generatedApi
    ↓
enhancedApi
    ↓
feature modules

Такой подход:

  • изолирует generated code;
  • упрощает обновления;
  • снижает конфликты;
  • делает API масштабируемым;
  • упрощает поддержку large-scale приложений.