Генератор RTK Query предназначен для автоматического создания API-клиентов, endpoints, hooks и типов на основе внешних схем API. Основная задача генератора — устранение ручного написания однотипного кода и синхронизация frontend-слоя с серверным контрактом.
Наиболее распространённые источники генерации:
Генерация особенно важна в крупных проектах, где:
Для генерации 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/
Генератор расширяет существующий 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: () => ({}),
})
Такой подход особенно удобен при:
Создаётся отдельный конфигурационный файл.
// 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,
})
Путь до OpenAPI-схемы.
Поддерживаются:
schemaFile: './openapi.json'
schemaFile: 'https://example.com/openapi.json'
schemaFile: './swagger.yaml'
Файл с базовым API.
apiFile: './src/services/emptyApi.ts'
Имя экспортируемого API.
apiImport: 'emptyApi'
Генератор импортирует именно этот объект:
import { emptyApi } from './emptyApi'
Файл, в который будет записан результат генерации.
outputFile: './src/services/generatedApi.ts'
Имя экспортируемого generated API.
exportName: 'generatedApi'
Результат:
export const generatedApi = emptyApi.injectEndpoints(...)
Автоматическая генерация React hooks.
hooks: true
После генерации появляются:
useGetUsersQuery
useCreateUserMutation
useUpdatePostMutation
npx @rtk-query/codegen-openapi ./src/services/openapi-config.ts
{
"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
// 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
),
})
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.
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 немедленно покажет ошибку при неправильных параметрах.
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',
},
})
Сгенерированные endpoints можно расширять вручную.
export const extendedApi =
generatedApi.enhanceEndpoints({
endpoints: {
getUsers: {
transformResponse: (response) => {
return response.items
},
},
},
})
Часто generated API дополняется кеш-инвалидацией.
export const emptyApi = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: '/api',
}),
tagTypes: ['Users', 'Posts'],
endpoints: () => ({}),
})
export const enhancedApi =
generatedApi.enhanceEndpoints({
endpoints: {
getUsers: {
providesTags: ['Users'],
},
createUser: {
invalidatesTags: ['Users'],
},
},
})
Иногда hooks не нужны.
Например:
hooks: false
Хорошей практикой считается разделение:
Только автогенерация.
import { generatedApi } from './generatedApi'
export const extendedApi =
generatedApi.enhanceEndpoints({
endpoints: {
getUsers: {
providesTags: ['Users'],
},
},
})
При следующем запуске генератора изменения будут потеряны.
Обычно generated файлы:
Пример:
- name: Generate RTK Query API
run: npm run generate-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-схемы:
schemaFile: './swagger.yaml'
Это особенно распространено в:
Иногда 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
},
})
Все generated endpoints автоматически используют базовый baseQuery.
export const emptyApi = createApi({
reducerPath: 'api',
baseQuery: customBaseQuery,
endpoints: () => ({}),
})
OpenAPI enum:
status:
type: string
enum:
- active
- blocked
Сгенерированный тип:
export type Status =
| 'active'
| 'blocked'
OpenAPI:
email:
type: string
nullable: true
Результат:
email?: string | null
OpenAPI:
parameters:
- in: query
name: page
schema:
type: integer
Результат:
useGetUsersQuery({
page: 1,
})
Generated endpoint:
query: (queryArg) => ({
url: `/users`,
params: {
page: queryArg.page,
},
})
/users/{id}
Результат:
query: (queryArg) => ({
url: `/users/${queryArg.id}`,
})
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserDto'
Результат:
body: queryArg.userDto
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/UserDto'
Результат:
build.query<UserDto, ...>
Наиболее распространённая проблема.
Ошибки возникают при:
Наиболее стабильна поддержка:
Swagger 2.0 иногда требует преобразования.
Крупные API могут генерировать тысячи строк кода.
Распространённые решения:
Некоторые команды используют generator исключительно для:
Бизнес-логика остаётся ручной.
Типичный сценарий:
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
Такой подход: