api-client-patterns

HTTP client patterns, API integration, request/response handling, error handling, retry logic, axios usage. Use when building API clients, integrating external services, handling API errors, or making HTTP requests.

16 stars

Best use case

api-client-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

HTTP client patterns, API integration, request/response handling, error handling, retry logic, axios usage. Use when building API clients, integrating external services, handling API errors, or making HTTP requests.

Teams using api-client-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/api-client-patterns/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/backend/api-client-patterns/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/api-client-patterns/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How api-client-patterns Compares

Feature / Agentapi-client-patternsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

HTTP client patterns, API integration, request/response handling, error handling, retry logic, axios usage. Use when building API clients, integrating external services, handling API errors, or making HTTP requests.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# API Client Patterns

## Core Principles

1. **Single Responsibility** - One client per service
2. **Centralized Configuration** - Base URL, headers, timeouts in one place
3. **Comprehensive Error Handling** - Catch and transform errors appropriately
4. **Type Safety** - Define request/response interfaces
5. **Retry Logic** - Handle transient failures gracefully

---

## API Client Structure

### CORRECT: Well-Structured API Client

```javascript
// client/src/utils/api.js
import axios from 'axios';

// Configuration
const API_BASE = import.meta.env.VITE_API_BASE || 'http://localhost:3001/api';
const TIMEOUT = 30000; // 30 seconds

// Create axios instance with defaults
const apiClient = axios.create({
  baseURL: API_BASE,
  timeout: TIMEOUT,
  headers: {
    'Content-Type': 'application/json'
  }
});

// Request interceptor (optional - for auth, logging, etc.)
apiClient.interceptors.request.use(
  (config) => {
    // Add auth token if available
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// Response interceptor (optional - for error transformation)
apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    // Transform errors to consistent format
    const message = error.response?.data?.error || error.message || 'Unknown error';
    return Promise.reject(new Error(message));
  }
);

// API Methods

/**
 * Verify faculty password
 */
export const verifyPassword = async (password) => {
  const response = await apiClient.post('/auth/verify', { password });
  return response.data;
};

/**
 * Generate or update instructional page
 */
export const generatePage = async (config, message, history = []) => {
  const response = await apiClient.post('/generate', {
    config,
    message,
    history
  });
  return response.data;
};

/**
 * Generate AI image using DALL-E
 */
export const generateImage = async (prompt) => {
  const response = await apiClient.post('/images/generate', { prompt });
  return response.data;
};

/**
 * Upload image to Cloudinary
 */
export const uploadImage = async (file) => {
  const formData = new FormData();
  formData.append('image', file);

  const response = await apiClient.post('/images/upload', formData, {
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  });
  return response.data;
};

export default apiClient;
```

### WRONG: Scattered API Calls

```javascript
// ❌ DON'T DO THIS: Direct axios calls scattered across components
import axios from 'axios';

// In Component 1
const handleLogin = async () => {
  const res = await axios.post('http://localhost:3001/api/auth/verify', {
    password: pwd
  });
};

// In Component 2
const handleGenerate = async () => {
  const res = await axios.post('http://localhost:3001/api/generate', data);
};

// ❌ Issues:
// - Repeated base URL strings
// - No centralized error handling
// - No configuration reuse
// - Hard to test
```

---

## Error Handling Patterns

### CORRECT: Comprehensive Error Handling

```javascript
// API client with error transformation
export const generatePage = async (config, message, history) => {
  try {
    const response = await apiClient.post('/generate', {
      config,
      message,
      history
    });
    return response.data;
  } catch (error) {
    // Check different error types
    if (error.response) {
      // Server responded with error status
      const status = error.response.status;
      const message = error.response.data?.error || 'Server error';

      if (status === 400) {
        throw new Error(`Validation error: ${message}`);
      } else if (status === 401) {
        throw new Error('Authentication failed');
      } else if (status === 429) {
        throw new Error('Rate limit exceeded. Please try again later.');
      } else if (status >= 500) {
        throw new Error('Server error. Please try again.');
      } else {
        throw new Error(message);
      }
    } else if (error.request) {
      // Request made but no response received
      throw new Error('Network error. Please check your connection.');
    } else {
      // Something else happened
      throw new Error(error.message || 'Request failed');
    }
  }
};
```

### Component Error Handling

```javascript
// In component
export default function ChatInterface() {
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);

  const handleSubmit = async () => {
    setLoading(true);
    setError(null);

    try {
      const result = await generatePage(config, message, history);
      // Handle success
    } catch (err) {
      // Error is already transformed by API client
      setError(err.message);

      // Optional: Report to error tracking service
      console.error('Generation failed:', err);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      {error && (
        <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
          <p>{error}</p>
          <button onClick={() => setError(null)}>Dismiss</button>
        </div>
      )}
      {/* Rest of component */}
    </div>
  );
}
```

---

## Request Configuration Patterns

### With Timeout

```javascript
export const generatePageWithTimeout = async (config, message, history, timeout = 60000) => {
  const response = await apiClient.post('/generate', {
    config,
    message,
    history
  }, {
    timeout // Override default timeout for long operations
  });
  return response.data;
};
```

### With Abort Controller (Cancellation)

```javascript
export const generatePageCancellable = async (config, message, history, signal) => {
  const response = await apiClient.post('/generate', {
    config,
    message,
    history
  }, {
    signal // Pass AbortController signal
  });
  return response.data;
};

// Usage in component
const abortController = useRef(null);

const handleGenerate = async () => {
  // Cancel previous request if exists
  if (abortController.current) {
    abortController.current.abort();
  }

  abortController.current = new AbortController();

  try {
    const result = await generatePageCancellable(
      config,
      message,
      history,
      abortController.current.signal
    );
    // Handle result
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('Request cancelled');
    } else {
      setError(err.message);
    }
  }
};

// Cleanup on unmount
useEffect(() => {
  return () => {
    if (abortController.current) {
      abortController.current.abort();
    }
  };
}, []);
```

---

## Streaming Responses Pattern

For long-running AI generation with streaming:

```javascript
/**
 * Generate page with streaming response
 */
export const generatePageStream = async (config, message, history, onChunk) => {
  const response = await fetch(`${API_BASE}/generate/stream`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ config, message, history })
  });

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

  while (true) {
    const { done, value } = await reader.read();

    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split('\n');
    buffer = lines.pop(); // Keep incomplete line in buffer

    for (const line of lines) {
      if (line.trim()) {
        try {
          const data = JSON.parse(line);
          onChunk(data);
        } catch (err) {
          console.error('Failed to parse chunk:', err);
        }
      }
    }
  }
};

// Usage in component
const handleStreamingGenerate = async () => {
  let fullText = '';

  try {
    await generatePageStream(config, message, history, (chunk) => {
      // Process each chunk as it arrives
      if (chunk.type === 'text') {
        fullText += chunk.content;
        setPreview(fullText); // Update UI in real-time
      } else if (chunk.type === 'done') {
        setComplete(true);
      }
    });
  } catch (err) {
    setError(err.message);
  }
};
```

---

## Retry Logic Pattern

For handling transient failures:

```javascript
/**
 * Retry a function with exponential backoff
 */
const retryWithBackoff = async (fn, maxRetries = 3, baseDelay = 1000) => {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      const isLastAttempt = attempt === maxRetries - 1;

      // Don't retry on 4xx errors (client errors)
      if (error.response?.status >= 400 && error.response?.status < 500) {
        throw error;
      }

      if (isLastAttempt) {
        throw error;
      }

      // Exponential backoff: 1s, 2s, 4s, 8s, etc.
      const delay = baseDelay * Math.pow(2, attempt);
      console.log(`Retry attempt ${attempt + 1}/${maxRetries} after ${delay}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
};

// Usage
export const generatePageWithRetry = async (config, message, history) => {
  return retryWithBackoff(async () => {
    const response = await apiClient.post('/generate', {
      config,
      message,
      history
    });
    return response.data;
  });
};
```

---

## File Upload Pattern

### With Progress Tracking

```javascript
export const uploadImageWithProgress = async (file, onProgress) => {
  const formData = new FormData();
  formData.append('image', file);

  const response = await apiClient.post('/images/upload', formData, {
    headers: {
      'Content-Type': 'multipart/form-data'
    },
    onUploadProgress: (progressEvent) => {
      const percentCompleted = Math.round(
        (progressEvent.loaded * 100) / progressEvent.total
      );
      onProgress(percentCompleted);
    }
  });

  return response.data;
};

// Usage in component
const handleFileUpload = async (file) => {
  setProgress(0);
  try {
    const result = await uploadImageWithProgress(file, (percent) => {
      setProgress(percent);
    });
    setImageUrl(result.url);
  } catch (err) {
    setError(err.message);
  } finally {
    setProgress(0);
  }
};
```

---

## Environment-Based Configuration

```javascript
// client/src/utils/api.js

// Different base URLs for different environments
const getApiBase = () => {
  const env = import.meta.env.MODE;

  switch (env) {
    case 'production':
      return 'https://api.yourapp.com';
    case 'staging':
      return 'https://staging-api.yourapp.com';
    case 'development':
    default:
      return import.meta.env.VITE_API_BASE || 'http://localhost:3001/api';
  }
};

const apiClient = axios.create({
  baseURL: getApiBase(),
  timeout: 30000
});
```

---

## Testing API Clients

```javascript
// client/src/utils/api.test.js
import { describe, it, expect, vi } from 'vitest';
import axios from 'axios';
import { generatePage, verifyPassword } from './api';

// Mock axios
vi.mock('axios');

describe('API Client', () => {
  it('should call generatePage endpoint with correct data', async () => {
    const mockResponse = {
      data: {
        html: '<div>Test</div>',
        message: 'Generated successfully'
      }
    };

    axios.create.mockReturnValue({
      post: vi.fn().mockResolvedValue(mockResponse)
    });

    const result = await generatePage(
      { depth: 2 },
      'Create a page about React',
      []
    );

    expect(result.html).toBe('<div>Test</div>');
  });

  it('should handle errors correctly', async () => {
    axios.create.mockReturnValue({
      post: vi.fn().mockRejectedValue(new Error('Network error'))
    });

    await expect(
      generatePage({}, 'Test', [])
    ).rejects.toThrow('Network error');
  });
});
```

---

## Checklist

### Before Creating an API Client
- [ ] What endpoints will this client access?
- [ ] What base URL and configuration is needed?
- [ ] What error cases need handling?
- [ ] Does it need retry logic?
- [ ] Does it need request/response interceptors?

### After Creating an API Client
- [ ] Base URL configured correctly
- [ ] Timeout set appropriately
- [ ] Errors transformed to consistent format
- [ ] All endpoints have JSDoc comments
- [ ] Request/response types documented
- [ ] Error cases handled in consuming components
- [ ] Tested with mock responses

---

## Integration with Other Skills

- **react-component-patterns**: Using API clients in components
- **express-api-patterns**: Understanding backend endpoints
- **systematic-debugging**: Debugging API integration issues
- **testing-patterns**: Testing API clients

---

## Common Mistakes to Avoid

1. ❌ Scattered axios calls across components
2. ❌ Hard-coded API URLs
3. ❌ Not handling network errors
4. ❌ Not transforming error responses
5. ❌ Missing timeout configuration
6. ❌ Not cancelling requests on unmount
7. ❌ Retry logic on non-retryable errors (4xx)
8. ❌ Not using environment variables for base URL

Related Skills

memory-safety-patterns

16
from diegosouzapw/awesome-omni-skill

Implement memory-safe programming with RAII, ownership, smart pointers, and resource management across Rust, C++, and C. Use when writing safe systems code, managing resources, or preventing memory...

llm-app-patterns

16
from diegosouzapw/awesome-omni-skill

Production-ready patterns for building LLM applications. Covers RAG pipelines, agent architectures, prompt IDEs, and LLMOps monitoring. Use when designing AI applications, implementing RAG, building agents, or setting up LLM observability.

dbt-transformation-patterns

16
from diegosouzapw/awesome-omni-skill

Master dbt (data build tool) for analytics engineering with model organization, testing, documentation, and incremental strategies. Use when building data transformations, creating data models, or ...

data-fetching-patterns

16
from diegosouzapw/awesome-omni-skill

Explains data fetching strategies including fetch on render, fetch then render, render as you fetch, and server-side data fetching. Use when implementing data loading, optimizing loading performance, or choosing between client and server data fetching.

airflow-dag-patterns

16
from diegosouzapw/awesome-omni-skill

Build production Apache Airflow DAGs with best practices for operators, sensors, testing, and deployment. Use when creating data pipelines, orchestrating workflows, or scheduling batch jobs.

ai-product-patterns

16
from diegosouzapw/awesome-omni-skill

Builds AI-native products using OpenAI's development philosophy and modern AI UX patterns. Use when integrating AI features, designing for model improvements, implementing evals as product specs, or creating AI-first experiences. Based on Kevin Weil (OpenAI CPO) on building for future models, hybrid approaches, and cost optimization.

a2a-executor-patterns

16
from diegosouzapw/awesome-omni-skill

Agent-to-Agent (A2A) executor implementation patterns for task handling, execution management, and agent coordination. Use when building A2A executors, implementing task handlers, creating agent execution flows, or when user mentions A2A protocol, task execution, agent executors, task handlers, or agent coordination.

ai-generation-client

16
from diegosouzapw/awesome-omni-skill

External AI API integration with retry logic, rate limiting, content safety detection, and multi-turn conversation support for image generation.

GitOps Patterns

16
from diegosouzapw/awesome-omni-skill

ArgoCD ApplicationSets, progressive delivery, Harness GitX, and multi-cluster GitOps patterns

dotnet-gha-patterns

16
from diegosouzapw/awesome-omni-skill

Composes GitHub Actions workflows. Reusable workflows, composite actions, matrix, caching.

bats-testing-patterns

16
from diegosouzapw/awesome-omni-skill

Comprehensive guide for writing shell script tests using Bats (Bash Automated Testing System). Use when writing or improving tests for Bash/shell scripts, creating test fixtures, mocking commands, or setting up CI/CD for shell script testing. Includes patterns for assertions, setup/teardown, mocking, fixtures, and integration with GitHub Actions.

bash-defensive-patterns

16
from diegosouzapw/awesome-omni-skill

Master defensive Bash programming techniques for production-grade scripts. Use when writing robust shell scripts, CI/CD pipelines, or system utilities requiring fault tolerance and safety.