Back to catalog

api-integration-layer

This skill creates type-safe API client layers with auto-retry, error handling, and loading states. Supports Fetch API, Axios, TanStack Query (React Query), with TypeScript types, interceptors, and caching. Generates API clients, request/response types, error handling, loading states, optimistic updates. Activate when user says "API client", "fetch data", "call backend", "integrate API", or needs HTTP requests.

  • Category: discovered
  • Tags: github, community
  • Version: 1.0.0
  • Author: walid
  • Downloads: 0
  • Updated: 2025-11-22

Download the latest version (v1.0.0) from this page.

SKILL.md

---
name: api-integration-layer
description: |
  This skill creates type-safe API client layers with auto-retry, error handling, and loading states.
  Supports Fetch API, Axios, TanStack Query (React Query), with TypeScript types, interceptors, and caching.
  Generates API clients, request/response types, error handling, loading states, optimistic updates.
  Activate when user says "API client", "fetch data", "call backend", "integrate API", or needs HTTP requests.
  Output: Type-safe API client with error handling, loading states, caching, and retry logic.
---

# API Integration Layer

> **Purpose**: Create type-safe API clients with error handling and caching

---

## When to Use

- ✅ Call backend APIs from frontend
- ✅ Need type-safe requests/responses
- ✅ Handle loading states, errors, retries
- ✅ Cache API responses
- ✅ User says: "API client", "fetch data", "call backend"

---

## TanStack Query (React Query)

```typescript
// api/users.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

interface User {
  id: number;
  name: string;
  email: string;
}

// API functions
async function fetchUsers(): Promise<User[]> {
  const response = await fetch('/api/users');
  if (!response.ok) throw new Error('Failed to fetch users');
  return response.json();
}

async function fetchUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  if (!response.ok) throw new Error('User not found');
  return response.json();
}

async function createUser(user: Omit<User, 'id'>): Promise<User> {
  const response = await fetch('/api/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(user)
  });
  if (!response.ok) throw new Error('Failed to create user');
  return response.json();
}

async function updateUser(id: number, user: Partial<User>): Promise<User> {
  const response = await fetch(`/api/users/${id}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(user)
  });
  if (!response.ok) throw new Error('Failed to update user');
  return response.json();
}

async function deleteUser(id: number): Promise<void> {
  const response = await fetch(`/api/users/${id}`, {
    method: 'DELETE'
  });
  if (!response.ok) throw new Error('Failed to delete user');
}

// React Query hooks
export function useUsers() {
  return useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
    staleTime: 5 * 60 * 1000, // 5 minutes
    gcTime: 10 * 60 * 1000 // 10 minutes
  });
}

export function useUser(id: number) {
  return useQuery({
    queryKey: ['users', id],
    queryFn: () => fetchUser(id),
    enabled: id > 0 // Only fetch if ID is valid
  });
}

export function useCreateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: createUser,
    onSuccess: () => {
      // Invalidate users list to refetch
      queryClient.invalidateQueries({ queryKey: ['users'] });
    }
  });
}

export function useUpdateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: ({ id, data }: { id: number; data: Partial<User> }) =>
      updateUser(id, data),
    onSuccess: (_, variables) => {
      queryClient.invalidateQueries({ queryKey: ['users'] });
      queryClient.invalidateQueries({ queryKey: ['users', variables.id] });
    }
  });
}

export function useDeleteUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: deleteUser,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] });
    }
  });
}
```

**Usage in Component**:
```tsx
import { useUsers, useCreateUser } from './api/users';

function UsersList() {
  const { data: users, isLoading, error } = useUsers();
  const createUser = useCreateUser();

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  const handleCreate = async () => {
    await createUser.mutateAsync({
      name: 'John Doe',
      email: 'john@example.com'
    });
  };

  return (
    <div>
      <button onClick={handleCreate} disabled={createUser.isPending}>
        {createUser.isPending ? 'Creating...' : 'Create User'}
      </button>

      <ul>
        {users?.map(user => (
          <li key={user.id}>{user.name} - {user.email}</li>
        ))}
      </ul>
    </div>
  );
}
```

---

## Axios Client with Interceptors

```typescript
// api/client.ts
import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';

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

// Request interceptor (add auth token)
apiClient.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    const token = localStorage.getItem('token');
    if (token && config.headers) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// Response interceptor (handle errors)
apiClient.interceptors.response.use(
  (response) => response,
  async (error: AxiosError) => {
    const originalRequest = error.config;

    // Refresh token on 401
    if (error.response?.status === 401 && originalRequest) {
      try {
        const refreshToken = localStorage.getItem('refreshToken');
        const response = await axios.post('/api/refresh', { refreshToken });

        const { token } = response.data;
        localStorage.setItem('token', token);

        // Retry original request
        if (originalRequest.headers) {
          originalRequest.headers.Authorization = `Bearer ${token}`;
        }
        return apiClient(originalRequest);
      } catch {
        // Refresh failed, logout
        localStorage.clear();
        window.location.href = '/login';
      }
    }

    return Promise.reject(error);
  }
);

export default apiClient;
```

**API Service**:
```typescript
// api/users.ts
import apiClient from './client';

interface User {
  id: number;
  name: string;
  email: string;
}

export const usersApi = {
  getAll: () => apiClient.get<User[]>('/users').then(res => res.data),

  getById: (id: number) =>
    apiClient.get<User>(`/users/${id}`).then(res => res.data),

  create: (user: Omit<User, 'id'>) =>
    apiClient.post<User>('/users', user).then(res => res.data),

  update: (id: number, user: Partial<User>) =>
    apiClient.put<User>(`/users/${id}`, user).then(res => res.data),

  delete: (id: number) =>
    apiClient.delete(`/users/${id}`).then(res => res.data)
};
```

---

**Skill Version**: 1.0.0
**Technologies**: TanStack Query, Axios, Fetch API
**Output**: Type-safe API client with caching, error handling, loading states