Receber por email
← Voltar para o catálogo

api-client

Use when setting up API clients - TanStack Query, Axios, JWT token management, error handling, or response parsing. NOT when plain fetch calls, non-API data handling, or unrelated UI logic. Triggers: "API client", "data fetching", "JWT token", "error handling", "paginated list", "TanStack Query".

367estrelas
Atualizado há 4 meses

Ver no GitHub ↗

Como adicionar

/plugin marketplace add aiskillstore/marketplace

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/api-client/svg)](https://www.skillteca.com.br/skills/api-client)

Skills relacionadas

Ver todas de Segurança →

Alerta por categoria

Receba novas skills de Segurança toda segunda

1 email curto, só com as skills novas de Segurança. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

API Client Skill

Overview

Expert guidance for API client implementation using TanStack Query/Axios, including JWT token attachment via interceptors, global error handling with toasts, type-safe response parsing with Zod, and offline detection for robust data fetching.

When This Skill Applies

This skill triggers when users request:

  • API Setup: "Setup API client", "Configure TanStack Query", "Axios instance"
  • Data Fetching: "Fetch student data", "Get attendance", "API calls"
  • JWT/Token: "Attach JWT token", "Bearer token headers", "Token refresh"
  • Error Handling: "API error toast", "Handle 401", "Retry failed requests"
  • Response Parsing: "Type-safe responses", "Zod validation", "Parse API data"
  • Pagination: "Paginated list", "Infinite query", "Load more data"

Core Rules

1. Setup: TanStack Query Configuration

// lib/queryClient.ts
import { QueryClient } from '@tanstack/react-query';

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 minutes
      gcTime: 10 * 60 * 1000, // 10 minutes
      retry: 3,
      retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
    },
    mutations: {
      retry: 1,
    },
  },
});

// app/layout.tsx or app/providers.tsx
'use client';
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from '@/lib/queryClient';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
}

Requirements:

  • Use TanStack Query v5 for data fetching
  • Configure appropriate staleTime and gcTime
  • Set retry strategy with exponential backoff
  • Wrap app with QueryClientProvider
  • Use Axios as fallback for complex scenarios

2. JWT: Interceptors Auto-Attach

// lib/apiClient.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import { useAuthStore } from '@/lib/auth-store';

class ApiClient {
  private client: AxiosInstance;

  constructor() {
    this.client = axios.create({
      baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api',
      timeout: 10000, // 10 seconds
    });

    this.setupInterceptors();
  }

  private setupInterceptors() {
    // Request interceptor - attach JWT token
    this.client.interceptors.request.use(
      (config: InternalAxiosRequestConfig) => {
        const { session } = useAuthStore.getState();
        if (session?.token && config.headers) {
          config.headers.Authorization = `Bearer ${session.token}`;
        }
        return config;
      },
      (error) => Promise.reject(error)
    );

    // Response interceptor - handle errors and 401
    this.client.interceptors.response.use(
      (response: AxiosResponse) => response,
      async (error) => {
        if (error.response?.status === 401) {
          const { refresh } = useAuthStore.getState();
          try {
            const newToken = await refresh();
            if (newToken) {
              error.config!.headers!.Authorization = `Bearer ${newToken}`;
              return this.client(error.config!);
            }
          } catch (refreshError) {
            useAuthStore.getState().signOut();
            window.location.href = '/auth/login';
          }
        }
        return Promise.reject(error);
      }
    );
  }

  get<T>(url: string, config?: AxiosRequestConfig) {
    return this.client.get<T>(url, config);
  }

  post<T>(url: string, data?: any, config?: AxiosRequestConfig) {
    return this.client.post<T>(url, data, config);
  }

  put<T>(url: string, data?: any, config?: AxiosRequestConfig) {
    return this.client.put<T>(url, data, config);
  }

  delete<T>(url: string, config?: AxiosRequestConfig) {
    return this.client.delete<T>(url, config);
  }
}

export const apiClient = new ApiClient();

Requirements:

  • Create Axios instance with baseURL and timeout
  • Request interceptor attaches JWT from auth store
  • Response interceptor handles 401 and token refresh
  • Automatic redirect to login on refresh failure
  • Type-safe methods with TypeScript generics

3. Errors: Global Handler

// lib/errorHandler.ts
import axios from 'axios';
import { toast } from 'sonner';

export const handleApiError = (error: any) => {
  if (axios.isAxiosError(error)) {
    const message = error.response?.data?.message || error.message;

    switch (error.response?.status) {
      case 400:
        toast.error('Bad Request', { description: message });
        break;
      case 401:
        toast.error('Unauthorized', { description: 'Please log in again' });
        break;
      case 403:
        toast.error('Forbidden', { description: 'You do not have permission' });
        break;
      case 404:
        toast.error('Not Found', { description: message });
        break;
      case 429:
        toast.error('Too Many Requests', { description: 'Please try again later' });
        break;
      case 500:
        toast.error('Server Error', { description: message });
        break;
      default:
        toast.error('Error', { description: message || 'Something went wrong' });
    }
  } else {
    toast.error('Network Error', { description: error.message || 'Something went wrong' });
  }
};
// hooks/useApi.ts
import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
import { apiClient } from '@/lib/apiClient';
import { handleApiError } from '@/lib/errorHandler';
import { z } from 'zod';

export function useApi<T>(
  queryKey: any[],
  url: string,
  options?: Omit<UseQueryOptions<T>, 'queryKey' | 'queryFn'>
) {
  return useQuery({
    queryKey,
    queryFn: async () => {
      const response = await apiClient.get<T>(url);
      return response.data;
    },
    ...options,
  });
}

export function useApiMutation<T, V = any>(
  url: string,
  options?: Omit<UseMutationOptions<T, V, void>, 'mutationFn'>,
  schema?: z.ZodSchema<T>
) {
  return useMutation({
    mutationFn: async (variables: V) => {
      const response = await apiClient.post<T>(url, variables);

      // Zod validation if schema provided
      if (schema) {
        try {
          const parsed = schema.parse(response.data);
          return parsed;
        } catch (error) {
          if (error instanceof z.ZodError) {
            toast.error('Validation Error', { description: error.errors[0].message });
            throw new Error(`Response validation failed: ${error.errors[0].message}`);
          }
        }
      }

      return response.data;
    },
    onError: (error) => {
      options?.onError?.(error);
      handleApiError(error);
    },
    onSuccess: (data, variables) => {
      options?.onSuccess?.(data, variables);
      if (options?.context?.successMessage) {
        toast.success('Success', { description: options.context.successMessage });
      }
    },
  });
}

Requirements:

  • Global error handler with toast notifications
  • Handle all HTTP status codes appropriately
  • Zod schema validation for response parsing
  • Automatic error display in toasts
  • Success message handling for mutations

4. Parsing: Typed Responses, Optimistic Updates

// lib/api/types.ts
import { z } from 'zod';

// Student type with Zod schema
export const StudentSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
  role: z.enum(['student', 'teacher', 'admin']),
  classId: z.string().nullable(),
  createdAt: z.string(),
  updatedAt: z.string(),
});

export type Student = z.infer<typeof StudentSchema>;

// Attendance type
export const AttendanceSchema = z.object({
  id: z.string(),
  studentId: z.string(),
  date: z.string(),
  status: z.enum(['present', 'absent', 'late']),
  notes: z.string().optional(),
});

export type Attendance = z.infer<typeof AttendanceSchema>;

// 
Compartilhar X LinkedIn