API Integration Examples

Practical code examples for real-world API integration

15 min readDeveloper Guide

Related Tools

Why API Integration Matters

Modern applications rarely work alone. They connect to payment gateways, email services, cloud storage, AI APIs, and countless other services. Understanding how to properly integrate APIs is essential for every developer.

This guide provides practical, copy-paste-ready code examples for common API integration scenarios: making requests, handling authentication, processing responses, dealing with errors, and implementing pagination.

1. Basic API Calls with Fetch

Simple GET Request

// Basic GET request
fetch('https://api.example.com/users')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

// Modern async/await version
async function fetchUsers() {
  try {
    const response = await fetch('https://api.example.com/users');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Failed to fetch users:', error);
    throw error;
  }
}

Always use async/await for cleaner, more readable code. It makes error handling straightforward with try/catch blocks.

POST Request with JSON Body

// POST request with JSON body
async function createUser(userData) {
  try {
    const response = await fetch('https://api.example.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(userData),
    });

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

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Failed to create user:', error);
    throw error;
  }
}

// Usage
const newUser = await createUser({
  name: 'John Doe',
  email: '[email protected]',
});

2. Authentication Methods

Bearer Token Authentication (Most Common)

Bearer tokens are the most widely used authentication method for modern APIs. They're typically obtained through OAuth 2.0 flows or API login endpoints.

// Bearer token authentication
async function fetchWithBearerToken(url, token) {
  const response = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
  });
  return response.json();
}

// Usage with stored token
const token = localStorage.getItem('api_token');
const data = await fetchWithBearerToken('https://api.example.com/data', token);

// Example: GitHub API
async function getGitHubUser(username, token) {
  const response = await fetch(`https://api.github.com/users/${username}`, {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Accept': 'application/vnd.github.v3+json',
    },
  });
  return response.json();
}

API Key Authentication

API keys are simple tokens used for service-to-service communication. They can be passed in headers, query parameters, or request body.

// API key in header (recommended)
async function fetchWithApiKey(url, apiKey) {
  const response = await fetch(url, {
    headers: {
      'X-API-Key': apiKey,
      'Content-Type': 'application/json',
    },
  });
  return response.json();
}

// API key in query parameter (some APIs require this)
async function fetchWithApiKeyQuery(url, apiKey) {
  const response = await fetch(`${url}?api_key=${apiKey}`);
  return response.json();
}

// Example: OpenAI API
async function callOpenAI(prompt, apiKey) {
  const response = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'gpt-3.5-turbo',
      messages: [{ role: 'user', content: prompt }],
    }),
  });
  return response.json();
}

HTTP Basic Authentication

Basic Auth encodes username:password in Base64. Use only with HTTPS. Modern APIs often use Basic Auth with API keys instead of actual passwords.

// Basic Auth with Base64 encoding
async function fetchWithBasicAuth(url, username, password) {
  // Encode credentials
  const credentials = btoa(`${username}:${password}`);

  const response = await fetch(url, {
    headers: {
      'Authorization': `Basic ${credentials}`,
      'Content-Type': 'application/json',
    },
  });
  return response.json();
}

// Usage
const data = await fetchWithBasicAuth(
  'https://api.example.com/data',
  'my_username',
  'my_api_key'  // Often APIs use API key as "password"
);

Security Warning

Basic Auth credentials are easily decoded. Always use HTTPS. Consider OAuth 2.0 or Bearer tokens for production applications.

3. Robust Error Handling

Complete Error Handling Pattern

class ApiError extends Error {
  constructor(message, status, data) {
    super(message);
    this.status = status;
    this.data = data;
    this.name = 'ApiError';
  }
}

async function apiRequest(url, options = {}) {
  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        ...options.headers,
      },
    });

    // Parse response
    const data = await response.json();

    // Check for HTTP errors
    if (!response.ok) {
      throw new ApiError(
        data.message || `HTTP error! status: ${response.status}`,
        response.status,
        data
      );
    }

    return data;
  } catch (error) {
    // Network errors (no connection)
    if (error.name === 'TypeError' && error.message.includes('fetch')) {
      throw new ApiError('Network error: Unable to connect to API', 0, null);
    }

    // JSON parse errors
    if (error.name === 'SyntaxError') {
      throw new ApiError('Invalid JSON response from API', 0, null);
    }

    // Re-throw ApiError
    throw error;
  }
}

// Usage with error handling
try {
  const data = await apiRequest('https://api.example.com/data');
  console.log(data);
} catch (error) {
  if (error instanceof ApiError) {
    switch (error.status) {
      case 401:
        console.error('Authentication required');
        // Redirect to login
        break;
      case 403:
        console.error('Access forbidden');
        break;
      case 404:
        console.error('Resource not found');
        break;
      case 500:
        console.error('Server error, try again later');
        break;
      default:
        console.error('API error:', error.message);
    }
  }
}
Common Status Codes
  • 400 - Bad Request (invalid input)
  • 401 - Unauthorized (need auth)
  • 403 - Forbidden (no permission)
  • 404 - Not Found
  • 429 - Too Many Requests (rate limit)
  • 500 - Server Error
Error Response Format
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid email",
    "details": {
      "field": "email",
      "value": "invalid@"
    }
  }
}

4. Handling Pagination

Paginated API Requests

Most APIs return large datasets in pages. Common patterns include offset/limit, page/per_page, or cursor-based pagination.

// Offset/limit pagination
async function fetchAllPages(baseUrl) {
  const limit = 100;
  let offset = 0;
  let allData = [];
  let hasMore = true;

  while (hasMore) {
    const url = `${baseUrl}?limit=${limit}&offset=${offset}`;
    const response = await fetch(url);
    const data = await response.json();

    allData.push(...data.items);

    // Check if more pages exist
    hasMore = data.items.length === limit;
    offset += limit;
  }

  return allData;
}

// Cursor-based pagination (recommended for large datasets)
async function fetchWithCursor(baseUrl, cursor = null) {
  const url = cursor
    ? `${baseUrl}?cursor=${cursor}`
    : baseUrl;

  const response = await fetch(url);
  const data = await response.json();

  return {
    items: data.items,
    nextCursor: data.next_cursor,
    hasMore: data.has_more,
  };
}

// Fetch all pages with cursor
async function fetchAllWithCursor(baseUrl) {
  let allData = [];
  let cursor = null;
  let hasMore = true;

  while (hasMore) {
    const result = await fetchWithCursor(baseUrl, cursor);
    allData.push(...result.items);
    cursor = result.nextCursor;
    hasMore = result.hasMore;
  }

  return allData;
}

Best practice: Cursor-based pagination is more reliable for large, frequently-changing datasets. Offset pagination can miss or duplicate items if data changes while paginating.

5. Rate Limiting & Retry

Handling Rate Limits with Retry

// Retry with exponential backoff
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  let retries = 0;

  while (retries < maxRetries) {
    try {
      const response = await fetch(url, options);

      // Rate limited - wait and retry
      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After');
        const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : 1000 * Math.pow(2, retries);

        console.log(`Rate limited, waiting ${waitTime}ms...`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
        retries++;
        continue;
      }

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

      return await response.json();
    } catch (error) {
      retries++;
      if (retries >= maxRetries) {
        throw error;
      }

      // Exponential backoff: 1s, 2s, 4s...
      const waitTime = 1000 * Math.pow(2, retries);
      await new Promise(resolve => setTimeout(resolve, waitTime));
    }
  }
}

// Usage
const data = await fetchWithRetry(
  'https://api.example.com/data',
  { headers: { 'Authorization': 'Bearer token' } },
  3  // max retries
);

Exponential backoff prevents hammering the API during outages. Start with 1 second, then 2, 4, 8... Most APIs return Retry-After header.

6. Using Axios (Alternative to Fetch)

Axios Examples

Axios is a popular HTTP client with automatic JSON parsing, better error handling, interceptors, and request cancellation.

import axios from 'axios';

// Create instance with default config
const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${localStorage.getItem('token')}`,
  },
});

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

// Response interceptor (handle errors globally)
api.interceptors.response.use(
  response => response.data,
  error => {
    if (error.response?.status === 401) {
      // Redirect to login
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

// Usage
const users = await api.get('/users');
const newUser = await api.post('/users', { name: 'John', email: '[email protected]' });
Axios Advantages
  • • Automatic JSON parsing
  • • Better error objects
  • • Request/response interceptors
  • • Request cancellation
  • • Timeout support
  • • Progress monitoring
Fetch Advantages
  • • Built-in (no dependency)
  • • Stream support
  • • Works in Service Workers
  • • Lighter weight
  • • Better for edge cases
  • • Native browser API

API Integration Best Practices

Always Handle Errors

Never assume API calls succeed. Handle network errors, HTTP errors, and JSON parse errors separately. Show meaningful messages to users.

Use HTTPS Always

Never send credentials or sensitive data over HTTP. Even Basic Auth requires HTTPS to be secure. Most APIs reject non-HTTPS requests anyway.

Store Tokens Securely

Use secure storage for tokens. In browsers, consider sessionStorage over localStorage for sensitive tokens. In apps, use secure storage APIs.

Implement Rate Limit Handling

Respect API rate limits. Implement exponential backoff for retries. Cache responses when possible to reduce API calls.

Validate Responses

Don't trust API responses blindly. Validate structure and types. Use TypeScript interfaces or JSON Schema for validation.

Related Guides