Documentation
Data Fetching
API Services

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;