API Services
Setting up API clients and service layers.
API Client Setup
// services/api/apiClient.ts
import axios, { type AxiosInstance, type AxiosError } from 'axios';
import { useAuthStore } from '@/stores/authStore';
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
export const apiClient: AxiosInstance = axios.create({
baseURL: API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor - Add auth token
apiClient.interceptors.request.use(
(config) => {
const token = useAuthStore.getState().token;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor - Handle errors
apiClient.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
if (error.response?.status === 401) {
useAuthStore.getState().logout();
window.location.href = '/login';
}
return Promise.reject(error);
}
);Service Pattern
Create service objects for each domain:
// features/users/services/userService.ts
import { apiClient } from '@/services/api/apiClient';
import type { User, CreateUserDTO, UpdateUserDTO, UserFilters } from '../types';
export const userService = {
getUsers: async (filters?: UserFilters): Promise<User[]> => {
const { data } = await apiClient.get('/users', { params: filters });
return data;
},
getUser: async (id: string): Promise<User> => {
const { data } = await apiClient.get(`/users/${id}`);
return data;
},
createUser: async (userData: CreateUserDTO): Promise<User> => {
const { data } = await apiClient.post('/users', userData);
return data;
},
updateUser: async (id: string, userData: UpdateUserDTO): Promise<User> => {
const { data } = await apiClient.patch(`/users/${id}`, userData);
return data;
},
deleteUser: async (id: string): Promise<void> => {
await apiClient.delete(`/users/${id}`);
},
};Type-Safe API Responses
// types/api.types.ts
export interface ApiResponse<T> {
data: T;
message?: string;
}
export interface PaginatedResponse<T> {
data: T[];
pagination: {
page: number;
pageSize: number;
total: number;
totalPages: number;
};
}
export interface ApiError {
message: string;
code: string;
statusCode: number;
details?: Record<string, string[]>;
}Paginated Service
// services/userService.ts
export const userService = {
getUsersPaginated: async (
params: PaginationParams & UserFilters
): Promise<PaginatedResponse<User>> => {
const { data } = await apiClient.get('/users', { params });
return data;
},
};
// Usage with TanStack Query
export const useUsersPaginated = (
page: number,
pageSize: number,
filters?: UserFilters
) => {
return useQuery({
queryKey: queryKeys.users.list({ page, pageSize, ...filters }),
queryFn: () => userService.getUsersPaginated({ page, pageSize, ...filters }),
placeholderData: keepPreviousData, // Keep previous data while loading
});
};File Upload Service
// services/uploadService.ts
export const uploadService = {
uploadFile: async (file: File, onProgress?: (percent: number) => void) => {
const formData = new FormData();
formData.append('file', file);
const { data } = await apiClient.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (progressEvent) => {
if (progressEvent.total) {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
onProgress?.(percent);
}
},
});
return data;
},
uploadMultiple: async (files: File[]) => {
const formData = new FormData();
files.forEach((file) => formData.append('files', file));
const { data } = await apiClient.post('/upload/multiple', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
return data;
},
};API Constants
// constants/apiEndpoints.ts
export const API_ENDPOINTS = {
AUTH: {
LOGIN: '/auth/login',
LOGOUT: '/auth/logout',
REFRESH: '/auth/refresh',
ME: '/auth/me',
},
USERS: {
BASE: '/users',
DETAIL: (id: string) => `/users/${id}`,
AVATAR: (id: string) => `/users/${id}/avatar`,
},
POSTS: {
BASE: '/posts',
DETAIL: (id: string) => `/posts/${id}`,
COMMENTS: (id: string) => `/posts/${id}/comments`,
},
} as const;