API Client & Communication
Relevant source files
The following files were used as context for generating this wiki page:The API Client is a centralized module responsible for all HTTP communication between the frontend application and the backend server. Built on top of axios, it provides a standardized and robust layer for making API requests. Its key features include automatic authentication token management, transparent token refreshing, consistent error handling, and a set of helper functions for common HTTP methods, including variants that perform runtime response validation using Zod schemas.
This module is fundamental to the application's service layer architecture, where business logic and data fetching are separated from UI components. All data services, such as AdminProductsService and CategoriesService, rely on this client to interact with the backend API.
Sources: lib/api-client.ts, README.md, features/products/services/admin-products.service.ts
Core Architecture
The API client is a pre-configured axios instance exported from lib/api-client.ts. This singleton instance is configured with a base URL, timeout, and default headers, ensuring consistency across all API calls.
Sources: lib/api-client.ts:28-37
Configuration
The client's behavior is defined by the API_CONFIG object. These settings are sourced from environment variables where possible.
| Parameter | Default Value | Description |
|---|---|---|
BASE_URL | http://localhost:3000 | The base URL for all API requests. Configured via NEXT_PUBLIC_API_URL. |
TIMEOUT | 10000 (10s) | Default request timeout in milliseconds. |
RETRY_COUNT | 3 | The number of times to retry a failed request. (Note: This is defined but not currently implemented in the interceptors). |
RETRY_DELAY | 1000 (1s) | The delay between retries. (Note: This is defined but not currently implemented). |
Additionally, the axios instance includes a default Content-Type header of application/json and a specific ngrok-skip-browser-warning header to improve the development experience when using ngrok.
Sources: lib/api-client.ts:23-37
Interceptors
Interceptors are the core of the client's advanced functionality, automatically handling authentication and response processing.
Request Interceptor
This interceptor runs before any request is sent. Its sole responsibility is to attach the JWT access token to the Authorization header if a token exists in the useAuthStore. This automates the process of authenticating API requests.
// lib/api-client.ts:42-51
apiClient.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const token = useAuthStore.getState().accessToken;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
// ...
);
Sources: lib/api-client.ts:42-51
Response Interceptor & Token Refresh
The response interceptor handles global response logic, most notably the automatic refresh of expired authentication tokens. When the API returns a 401 Unauthorized status, the interceptor attempts to obtain a new access token using the stored refresh token.
The flow is as follows:
- An API request fails with a
401status. - The interceptor catches the error and checks if it's the first attempt (to prevent infinite loops).
- It retrieves the
refreshTokenfromuseAuthStore. - It sends a
POSTrequest to the/auth/refreshendpoint with therefreshToken. - If successful, it updates the
accessTokenandrefreshTokeninuseAuthStore. - It updates the
Authorizationheader of the original failed request with the newaccessToken. - It retries the original request.
- If the refresh fails, it clears all tokens from the store and redirects the user to the
/loginpage.
The diagram below illustrates the automatic token refresh sequence.
Sources: lib/api-client.ts:56-110
API Helper Functions
To simplify data fetching and enforce consistency, api-client.ts provides a suite of helper functions that wrap the core axios methods. These are categorized into standard and validated helpers.
Sources: lib/api-client.ts:182-299
Standard Helpers
These functions are direct wrappers around axios methods, returning the data property from the response.
| Function | HTTP Method | Description |
|---|---|---|
apiGet<T> | GET | Performs a GET request. |
apiPost<TResponse, TRequest> | POST | Performs a POST request. |
apiPut<TResponse, TRequest> | PUT | Performs a PUT request. |
apiPatch<TResponse, TRequest> | PATCH | Performs a PATCH request. |
apiDelete<T> | DELETE | Performs a DELETE request. |
These are used in scenarios requiring custom headers or request body formats, such as multipart/form-data uploads in the AdminProductsService.
// features/products/services/admin-products.service.ts:133-142
async create(data: AdminCreateProductRequest): Promise<Product> {
const formData = this.buildCreateFormData(data);
const response = await apiClient.post<Product>(
"/products/admin",
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
},
);
// ...
}
Sources: lib/api-client.ts:182-233, features/products/services/admin-products.service.ts:133-142
Validated Helpers
A key feature of the API client is its integration with Zod for runtime response validation. The "validated" helpers (apiGetValidated, apiPostValidated, etc.) accept a Zod schema as an argument. After receiving a response from the API, they parse the data against the schema. If validation fails, an error is thrown, ensuring that the data flowing into the application is type-safe and matches the expected structure.
This pattern is widely used in the service layer, as seen in CategoriesService.
// features/categories/services/categories.service.ts:80-86
async getAll(): Promise<Category[]> {
const response = await apiGetValidated<PaginatedCategoriesResponse>(
"/categories",
PaginatedCategoriesResponseSchemaTyped,
);
return response.categories;
}
The following diagram shows the flow of a validated API request.
Error Handling
The client provides a centralized function, getErrorMessage, to parse errors from various sources into a user-friendly string. It is designed to handle:
AxiosError: It inspects the response data for amessageproperty.- NestJS Validation Errors: It specifically checks if
responseData.messageis an array of strings (a common pattern for validation errors in NestJS) and joins them into a single message. - Standard
Errorobjects. - Any other unknown error types, providing a generic fallback message.
The handleApiError function is a wrapper around getErrorMessage that can optionally trigger a toast notification (currently logged to the console).
Sources: lib/api-client.ts:115-179
Service Layer Integration
The API client is the foundation of the application's service layer. Each feature-specific service (e.g., AdminProductsService, CategoriesService) uses the apiClient or its helpers to communicate with the backend. This architecture decouples data-fetching logic from the UI and promotes reusability.
The diagram below shows the relationship between the API client and the services that consume it.
Common API Data Structures
The API communication relies on standardized data structures defined in types/common.types.ts.
| Type | Description | Fields |
|---|---|---|
ApiError | Standard structure for error responses from the backend. | statusCode: number, message: string, error: string |
PaginatedResponse<T> | Standard wrapper for paginated list endpoints. | products: T[], total: number, page: number, pageSize: number, totalPages: number |
Note: The products key in PaginatedResponse is a slight misnomer, as it is used for paginated responses of other entities as well, but the type definition itself is generic.
Sources: types/common.types.ts:16-47