API Communication Layer
The API communication layer in this project provides a robust, type-safe bridge between the React frontend and the FastAPI backend. It is built upon a generated SDK that leverages Axios for HTTP requests, TanStack Query for state management, and a custom core infrastructure to handle authentication, error propagation, and request cancellation.
Global Configuration and Authentication
The foundation of the API layer is the OpenAPI configuration object defined in frontend/src/client/core/OpenAPI.ts. This object acts as a central registry for global request settings, including the base URL and authentication logic.
In frontend/src/main.tsx, the application initializes these settings using environment variables and local storage:
OpenAPI.BASE = import.meta.env.VITE_API_URL
OpenAPI.TOKEN = async () => {
return localStorage.getItem("access_token") || ""
}
The TOKEN property is particularly significant as it accepts an asynchronous function. This allows the API client to dynamically retrieve the latest Bearer token from localStorage for every request, ensuring that the Authorization header is always up-to-date without requiring manual injection in every service call.
The Request Lifecycle
Every API call initiated by the generated services (e.g., UsersService) passes through the core request function in frontend/src/client/core/request.ts. This function is responsible for:
- URL Construction: Combining
OpenAPI.BASEwith the specific endpoint path and query parameters. - Header Injection: Automatically adding the
Authorizationheader ifOpenAPI.TOKENis provided. - Body Serialization: Handling different request body types (JSON, FormData, etc.).
- Execution: Using an Axios instance to perform the actual network request.
The request function returns a CancelablePromise, a custom wrapper found in frontend/src/client/core/CancelablePromise.ts.
Request Cancellation
The use of CancelablePromise instead of a standard native Promise allows the frontend to abort pending requests. This is crucial in a React environment to prevent "state update on unmounted component" warnings and to save bandwidth when a user navigates away from a page before a request completes.
The implementation uses the standard AbortController internally, mapping the cancel() method of the promise to the controller's abort() signal.
Error Handling Strategy
The project implements a multi-layered error handling strategy to provide both global consistency and local flexibility.
The ApiError Class
When a request fails, the request function throws an ApiError (defined in frontend/src/client/core/ApiError.ts). This class extends the native Error and captures:
- The HTTP
statuscode. - The
statusText. - The
bodyof the response (often containing FastAPI's validation details). - The original
requestoptions.
Global Interceptors
Global error handling is managed via TanStack Query's cache configurations in frontend/src/main.tsx. This ensures that any error occurring in a query or mutation is processed by a central handler:
const handleApiError = (error: Error) => {
if (error instanceof ApiError && [401, 403].includes(error.status)) {
localStorage.removeItem("access_token")
window.location.href = "/login"
}
}
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: handleApiError,
}),
mutationCache: new MutationCache({
onError: handleApiError,
}),
})
This pattern specifically targets authentication failures (401 Unauthorized and 403 Forbidden), automatically clearing the invalid token and redirecting the user to the login page.
Parsing Backend Validation Errors
FastAPI typically returns validation errors in a specific JSON format (e.g., { "detail": [{ "msg": "..." }] }). The project provides a utility in frontend/src/utils.ts to extract these messages reliably:
function extractErrorMessage(err: ApiError): string {
if (err instanceof AxiosError) {
return err.message
}
const errDetail = (err.body as any)?.detail
if (Array.isArray(errDetail) && errDetail.length > 0) {
return errDetail[0].msg
}
return errDetail || "Something went wrong."
}
The handleError function in the same file uses this utility to bridge the gap between the raw ApiError and the UI's notification system (such as toast messages).
Integration with TanStack Query
The generated SDK methods are rarely called in isolation. Instead, they are wrapped in TanStack Query hooks. This integration allows the application to benefit from automatic caching, background revalidation, and a unified loading/error state.
For example, a mutation to add a user might look like this:
const mutation = useMutation({
mutationFn: (data: UserCreate) => UsersService.createUser({ requestBody: data }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["users"] })
},
onError: (err: ApiError) => {
handleError.call(showToast, err)
},
})
In this flow, the UsersService handles the request construction, the request function handles the execution and token injection, and TanStack Query manages the lifecycle and error propagation back to the UI.