docs/en/framework/ui/react/http-requests.md
//[doc-seo]
{
"Description": "Learn how HTTP requests are made in ABP React UI applications with Axios, runtime configuration, and ABP interceptors."
}
ABP React UI templates use Axios for HTTP requests. The generated app contains a shared Axios instance with ABP-specific request and response interceptors, plus typed API modules for backend endpoints.
The shared client is defined in src/lib/api/axios.ts and exported as api.
The Axios base URL is resolved at request time from runtime configuration:
export function getApiBaseUrl(): string {
const apiUrl = getApiUrl()
if (apiUrl.startsWith('http://') || apiUrl.startsWith('https://')) {
return apiUrl.replace(/\/$/, '') + '/api'
}
if (import.meta.env.DEV) {
return '/api'
}
return apiUrl.replace(/\/$/, '') + '/api'
}
The API URL comes from:
dynamic-env.json -> apis.default.urlVITE_API_URLsrc/env.ts generated fallbackIn microservice solutions, apis.default.url normally points to the Web Gateway. In layered and single-layer solutions, it normally points to the HTTP API host.
The template creates one shared instance:
export const api = axios.create({
baseURL: '',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json',
},
})
Use this instance for application API modules instead of creating new Axios clients. It centralizes ABP headers, authentication, tenant handling, language handling, and redirects.
Before each request, the template:
baseURL from runtime configuration.Authorization: Bearer <token> from the OIDC user.__tenant when the user has selected a tenant.Accept-Language from i18next.X-Requested-With.api.interceptors.request.use(async (config) => {
config.baseURL = getApiBaseUrl()
const user = await userManager.getUser()
if (user?.access_token) {
config.headers.Authorization = `Bearer ${user.access_token}`
}
const tenantId = sessionStorage.getItem('abp_tenant_id')
if (tenantId && !config.headers.__tenant) {
config.headers.__tenant = tenantId
}
if (i18n?.language) {
config.headers['Accept-Language'] =
config.headers['Accept-Language'] ?? i18n.language
}
return config
})
The response interceptor handles common authorization failures:
401 Unauthorized: redirects to login unless skipAuthRedirect is set.403 Forbidden: redirects to /403 unless skip403Redirect is set.api.interceptors.response.use(
(response) => response,
async (error) => {
const status = error.response?.status
if (status === 401 && !error.config?.skipAuthRedirect) {
await userManager.signinRedirect()
return Promise.reject(new Error('Unauthorized - redirecting to login'))
}
if (status === 403 && !error.config?.skip403Redirect) {
window.location.href = '/403'
return Promise.reject(new Error('Forbidden'))
}
return Promise.reject(error)
}
)
Use skipAuthRedirect or skip403Redirect for calls where the component should handle the error itself.
The template organizes backend calls under src/lib/api/. For example, the Books sample defines DTOs and functions in books.ts:
import { api } from './axios'
export interface PagedResultDto<T> {
items: T[]
totalCount: number
}
export interface BookDto {
id: string
name?: string
price: number
}
export async function getBooks(): Promise<PagedResultDto<BookDto>> {
const { data } = await api.get<PagedResultDto<BookDto>>('/app/book', {
params: {
maxResultCount: 10,
skipCount: 0,
},
})
return data
}
Notice that the API module calls /app/book, not /api/app/book. The shared Axios base URL already includes the /api prefix when needed.
The template uses TanStack Query for server state:
const { data, isLoading } = useQuery({
queryKey: ['books', skipCount],
queryFn: () =>
getBooks({
maxResultCount: 10,
skipCount,
sorting: 'creationTime desc',
}),
})
Mutations use useMutation and invalidate related queries after success:
const createMutation = useMutation({
mutationFn: createBook,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['books'] })
toast.success(t('AbpUi::SavedSuccessfully'))
},
})
Create a file under src/lib/api/:
import { api } from './axios'
export interface ProductDto {
id: string
name: string
}
export async function getProducts(): Promise<ProductDto[]> {
const { data } = await api.get<ProductDto[]>('/app/product')
return data
}
Then consume it from a component with TanStack Query:
const productsQuery = useQuery({
queryKey: ['products'],
queryFn: getProducts,
})
In development, Vite proxies /api, /connect, and /getEnvConfig. This lets the React app use same-origin paths while calls are forwarded to the backend, Auth Server, or gateway configured by VITE_API_URL and VITE_AUTH_URL.