Detailed guide on RTK Query architecture (Code Splitting).
Architectural Invariant: createApi is called only once — in shared/api/backend/base.api.ts. All entities inject their endpoints into this single baseApi (one cache, one tag registry). A separate createApi is allowed only for an external service with a different baseUrl.
For correct operation of invalidation providesTags / invalidatesTags, all possible tags are collected in a centralized config.
1export const ENUM_API_TAGS = {
2 USER: "User",
3 BOOKING_ORDERS: "Booking Orders",
4 // и другие теги...
5} as const;An action declared in shared, so baseApi does not depend on entities. The auth slice subscribes to it via extraReducers and resets the session.
1import { createAction } from "@reduxjs/toolkit";
2
3/** Диспатчится baseApi при 401; auth slice подписывается через extraReducers */
4export const sessionExpired = createAction("api/sessionExpired");
5
6// entities/auth/model/auth.slice.ts
7// extraReducers: (builder) => {
8// builder.addCase(sessionExpired, () => initialState);
9// }The only createApi in the project. Its base query wraps fetchBaseQuery: on 401 it dispatches sessionExpired() from shared, without importing entities.
1import {
2 type BaseQueryFn,
3 type FetchArgs,
4 type FetchBaseQueryError,
5 createApi,
6 fetchBaseQuery
7} from "@reduxjs/toolkit/query/react";
8import { ENV } from "@/shared/config";
9import { sessionExpired } from "./session-expired.action";
10import { ENUM_API_TAGS } from "./tags.config";
11
12const rawBaseQuery = fetchBaseQuery({
13 baseUrl: ENV.VITE_API_URL,
14 credentials: "include"
15});
16
17/** 401 → sessionExpired(); слой shared не импортирует entities */
18const baseQueryWithAuthGuard: BaseQueryFn<
19 string | FetchArgs,
20 unknown,
21 FetchBaseQueryError
22> = async (args, api, extraOptions) => {
23 const result = await rawBaseQuery(args, api, extraOptions);
24
25 if (result.error?.status === 401) {
26 api.dispatch(sessionExpired());
27 }
28 return result;
29};
30
31/** Единственный createApi проекта — все entities инжектят сюда */
32export const baseApi = createApi({
33 baseQuery: baseQueryWithAuthGuard,
34 reducerPath: "baseApi",
35 endpoints: () => ({}),
36 tagTypes: Object.values(ENUM_API_TAGS)
37});The final stage in the target entity. We import baseApi and call injectEndpoints. This code becomes the public service of the entity.
1import { baseApi, ENUM_API_TAGS } from "@/shared/api/backend";
2
3export const bookingOrderApi = baseApi.injectEndpoints({
4 endpoints: (builder) => ({
5 getBookingOrders: builder.query<TResponse, TFilters>({
6 query: (filters) => ({ url: "/booking/orders" }),
7 providesTags: [ENUM_API_TAGS.BOOKING_ORDERS]
8 })
9 })
10});