Skip to content
frontend-api-integration logo

Frontend API Integration

frontend-api-integration

Guide for building robust API integration layers in React/TypeScript applications. Use when implementing HTTP clients (axios/fetch), request/response interceptors, authentication token handling, error handling strategies, retry logic, request cancellation, or organizing API services. Triggers on ...

SKILL.md

Full skill instructions

Frontend API Integration

API Client Setup

Axios Instance with Interceptors

import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  timeout: 10000,
  headers: { 'Content-Type': 'application/​json' }
});

// Request interceptor - auth token injection
api.interceptors.request.use((config: InternalAxiosRequestConfig) => {
  const token = localStorage.getItem('accessToken');
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

// Response interceptor - error handling + token refresh
api.interceptors.response.use(
  (response) => response,
  async (error: AxiosError) => {
    const originalRequest = error.config;
    if (error.response?.status === 401 && originalRequest && !originalRequest._retry) {
      originalRequest._retry = true;
      const newToken = await refreshAccessToken();
      originalRequest.headers.Authorization = `Bearer ${newToken}`;
      return api(originalRequest);
    }
    return Promise.reject(error);
  }
);

Request Cancellation

// Per-request cancellation
const controller = new AbortController();
api.get('/​data', { signal: controller.signal });
controller.abort();

// Auto-timeout helper
function withTimeout(ms: number) {
  const controller = new AbortController();
  setTimeout(() => controller.abort(), ms);
  return controller.signal;
}

Retry Logic

async function withRetry<T>(
  fn: () => Promise<T>,
  retries = 3,
  delay = 1000
): Promise<T> {
  try {
    return await fn();
  } catch (error) {
    if (retries === 0) throw error;
    await new Promise(r => setTimeout(r, delay));
    return withRetry(fn, retries - 1, delay * 2);
  }
}

Service Layer Pattern

// services/​userService.ts
export const userService = {
  getById: (id: string) => api.get<User>(`/​users/​${id}`).then(r => r.data),
  create: (data: CreateUserDTO) => api.post<User>('/​users', data).then(r => r.data),
  update: (id: string, data: Partial<User>) => api.patch<User>(`/​users/​${id}`, data).then(r => r.data),
  delete: (id: string) => api.delete(`/​users/​${id}`)
};

Error Handling

// types/​api.ts
interface ApiError {
  message: string;
  code: string;
  status: number;
}

function parseError(error: unknown): ApiError {
  if (axios.isAxiosError(error)) {
    return {
      message: error.response?.data?.message ?? error.message,
      code: error.response?.data?.code ?? 'UNKNOWN',
      status: error.response?.status ?? 500
    };
  }
  return { message: 'Unknown error', code: 'UNKNOWN', status: 500 };
}

Reference Files