This guide explains how to integrate with the FlexPrice backend API in the frontend application. We use a hybrid approach combining Axios for HTTP requests and TanStack Query (React Query) for efficient data fetching, caching, and state management.
// src/core/api/client.ts
import axios from 'axios';
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_URL,
headers: {
'Content-Type': 'application/json',
},
});
// Add auth token to requests
apiClient.interceptors.request.use((config) => {
const token = localStorage.getItem('auth_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});// src/utils/api_requests/FeatureApi.ts
import { apiClient } from '@/core/api/client';
interface Feature {
id: string;
name: string;
description: string;
}
interface PaginatedResponse<T> {
items: T[];
pagination: {
total: number;
limit: number;
offset: number;
};
}
export const FeatureApi = {
getAllFeatures: async ({ limit, offset }: { limit: number; offset: number }) => {
const response = await apiClient.get<PaginatedResponse<Feature>>('/features', {
params: { limit, offset },
});
return response.data;
},
};// src/pages/product-catalog/features/Features.tsx
import { useQuery } from '@tanstack/react-query';
import { FeatureApi } from '@/api/FeatureApi';
const FeaturesPage = () => {
const { limit, offset, page } = usePagination();
const {
data: featureData,
isLoading,
isError,
} = useQuery({
queryKey: ['fetchFeatures', page],
queryFn: () => FeatureApi.getAllFeatures({ limit, offset }),
});
if (isLoading) return <Loader />;
if (isError) toast.error('Error fetching features');
return (
// ... render component
);
};// src/core/query/queryClient.ts
import { QueryClient } from '@tanstack/react-query';
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
retry: 1,
refetchOnWindowFocus: false,
},
},
});// src/App.tsx
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from '@/core/query/queryClient';
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* Your app components */}
</QueryClientProvider>
);
}// src/utils/api_requests/FeatureApi.ts
export const FeatureApi = {
// ... existing methods
createFeature: async (feature: Omit<Feature, 'id'>) => {
const response = await apiClient.post<Feature>('/features', feature);
return response.data;
},
};// src/components/molecules/FeatureForm.tsx
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { FeatureApi } from '@/api/FeatureApi';
const FeatureForm = () => {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: FeatureApi.createFeature,
onSuccess: () => {
// Invalidate and refetch features query
queryClient.invalidateQueries({ queryKey: ['fetchFeatures'] });
toast.success('Feature created successfully');
},
onError: (error) => {
toast.error('Failed to create feature');
},
});
const onSubmit = (data: FeatureFormData) => {
mutation.mutate(data);
};
return (
// ... form JSX
);
};// src/core/api/client.ts
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Handle unauthorized
auth.logout();
}
return Promise.reject(error);
},
);const { data, error } = useQuery({
queryKey: ['fetchFeatures'],
queryFn: FeatureApi.getAllFeatures,
onError: (error) => {
if (axios.isAxiosError(error)) {
switch (error.response?.status) {
case 401:
// Handle unauthorized
break;
case 404:
// Handle not found
break;
default:
// Handle other errors
}
}
},
});// Configure cache behavior
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // Data considered fresh for 5 minutes
cacheTime: 10 * 60 * 1000, // Cache kept for 10 minutes
refetchOnWindowFocus: false, // Don't refetch on window focus
},
},
});// src/hooks/usePagination.ts
export const usePagination = () => {
const [page, setPage] = useState(1);
const limit = 10;
const offset = (page - 1) * limit;
return { page, limit, offset, setPage };
};// src/core/api/__mocks__/apiClient.ts
jest.mock('../client', () => ({
apiClient: {
get: jest.fn(),
post: jest.fn(),
patch: jest.fn(),
delete: jest.fn(),
},
}));// src/components/__tests__/Features.test.tsx
import { renderHook } from '@testing-library/react-hooks';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
describe('Features', () => {
const queryClient = new QueryClient();
it('should fetch features', async () => {
const { result } = renderHook(
() => useQuery({
queryKey: ['fetchFeatures'],
queryFn: FeatureApi.getAllFeatures,
}),
{
wrapper: ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
),
}
);
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
});
});- Use TanStack Query for data fetching and caching
- Keep API services organized by domain
- Implement proper error handling and loading states
- Use TypeScript for type safety
- Implement proper pagination for large datasets
- Use optimistic updates for better UX
- Implement proper retry logic
- Use proper cache invalidation strategies
- Implement proper error boundaries
- Use proper loading states and skeletons
GET /api/v1/usage- Get usage metricsPOST /api/v1/usage/events- Log usage events
GET /api/v1/billing/invoices- Get invoicesPOST /api/v1/billing/subscription- Update subscription
GET /api/v1/users/me- Get current userPATCH /api/v1/users/me- Update user profile
| Code | Description | Action |
|---|---|---|
| 400 | Bad Request | Check request parameters |
| 401 | Unauthorized | Refresh token or re-login |
| 403 | Forbidden | Check user permissions |
| 404 | Not Found | Verify resource exists |
| 429 | Too Many Requests | Implement rate limiting |
| 500 | Server Error | Contact support |
- Always use HTTPS
- Implement proper token management
- Sanitize user inputs
- Use environment variables for sensitive data
- Implement proper CORS policies
- Implement request caching
- Use pagination for large datasets
- Implement request debouncing
- Use compression for large payloads
- Implement proper error boundaries