Fetch Client API Documentation - v1.1.1
    Preparing search index...

    Fetch Client API Documentation - v1.1.1

    Fetch API - TypeScript HTTP Client

    A lightweight, modern TypeScript HTTP client library built on the native fetch API. This library provides a powerful alternative to axios with advanced features like interceptors, automatic retries, comprehensive error handling, and full TypeScript support.

    • Lightweight: Built on native fetch API with zero dependencies
    • TypeScript Support: Full type safety with comprehensive type definitions
    • Interceptors: Request and response middleware for authentication, logging, and transformation
    • Automatic Retries: Configurable retry logic with exponential backoff
    • Error Handling: Comprehensive error management with detailed error codes
    • Response Parsing: Automatic response parsing based on content type
    • Timeout Support: Request timeout handling with customizable timeouts
    • Base URL Support: Configure base URLs for consistent API endpoints
    • Header Management: Global and per-request header configuration
    • Modern ES6+: Uses modern JavaScript features with excellent browser support

    The library provides the following exports organized by category:

    npm install @x-common/fetch-client
    
    yarn add @x-common/fetch-client
    
    pnpm add @x-common/fetch-client
    

    This package supports ESM, CommonJS, and Browser environments:

    import { Client, HTTP_STATUS } from '@x-common/fetch-client';
    import type { RequestConfig } from '@x-common/fetch-client';
    const { Client, HTTP_STATUS } = require('@x-common/fetch-client');
    
    <!-- For modern browsers (ESM) -->
    <script type="module">
    import { Client } from 'https://unpkg.com/@x-common/fetch-client/dist/browser/index.esm.min.js';
    const api = new Client({ baseURL: 'https://api.example.com' });
    </script>

    <!-- For older browsers (UMD) -->
    <script src="https://unpkg.com/@x-common/fetch-client/dist/browser/index.umd.min.js"></script>
    <script>
    const { Client } = FetchClient;
    const api = new Client({ baseURL: 'https://api.example.com' });
    </script>

    React/Next.js:

    import { Client } from '@x-common/fetch-client';

    const api = new Client({ baseURL: process.env.NEXT_PUBLIC_API_URL });

    Vue/Nuxt:

    import { Client } from '@x-common/fetch-client';

    export const $api = new Client({ baseURL: useRuntimeConfig().public.apiBase });

    Angular:

    import { Injectable } from '@angular/core';
    import { Client } from '@x-common/fetch-client';

    @Injectable({ providedIn: 'root' })
    export class ApiService extends Client {
    constructor() {
    super({ baseURL: environment.apiUrl });
    }
    }

    The package automatically resolves to the correct module format based on your environment.

    import { Client } from '@x-common/fetch-client';

    // Create a client instance
    const api = new Client({
    baseURL: 'https://api.example.com',
    timeout: 10000,
    headers: {
    'Content-Type': 'application/json'
    }
    });

    // Make requests
    const users = await api.get('/users');
    const newUser = await api.post('/users', {
    name: 'John Doe',
    email: 'john@example.com'
    });
    import { Client } from '@x-common/fetch-client';

    const api = new Client({
    baseURL: 'https://api.example.com',
    headers: {
    'Authorization': 'Bearer your-access-token',
    'Content-Type': 'application/json'
    }
    });

    // All requests will include the Authorization header
    const profile = await api.get('/profile');

    The Client constructor accepts a configuration object with the following options:

    interface ClientConfig {
    baseURL?: string; // Base URL for all requests
    timeout?: number; // Request timeout in milliseconds
    headers?: Record<string, string>; // Default headers for all requests
    retry?: RetryConfig; // Retry configuration
    responseType?: ResponseType; // Default response parsing type
    }
    // Minimal configuration
    const api = new Client();

    // Basic configuration
    const api = new Client({
    baseURL: 'https://api.example.com'
    });

    // Full configuration
    const api = new Client({
    baseURL: 'https://api.example.com',
    timeout: 15000,
    headers: {
    'User-Agent': 'MyApp/1.0.0',
    'Accept': 'application/json',
    'Authorization': 'Bearer token'
    },
    retry: {
    maxRetries: 3,
    delay: 1000
    },
    responseType: 'json'
    });

    The client supports all standard HTTP methods:

    // Simple GET request
    const users = await api.get('/users');

    // GET with query parameters
    const users = await api.get('/users', {
    params: { page: 1, limit: 10 }
    });

    // GET with custom headers
    const users = await api.get('/users', {
    headers: { 'Cache-Control': 'no-cache' }
    });

    POST Requests

    // POST with JSON data
    const newUser = await api.post('/users', {
    name: 'John Doe',
    email: 'john@example.com'
    });

    // POST with form data
    const formData = new FormData();
    formData.append('file', file);
    const upload = await api.post('/upload', formData);

    // POST with custom options
    const result = await api.post('/users', userData, {
    timeout: 30000,
    headers: { 'X-Custom-Header': 'value' }
    });
    // Update entire resource
    const updatedUser = await api.put('/users/123', {
    name: 'Jane Doe',
    email: 'jane@example.com',
    status: 'active'
    });
    // Partial update
    const updatedUser = await api.patch('/users/123', {
    status: 'inactive'
    });
    // Delete resource
    await api.delete('/users/123');

    // Delete with confirmation
    await api.delete('/users/123', {
    headers: { 'X-Confirm': 'true' }
    });
    // Get headers only
    const headers = await api.head('/users/123');

    Each request method accepts an optional configuration object:

    interface RequestOptions {
    headers?: Record<string, string>; // Request-specific headers
    timeout?: number; // Request-specific timeout
    retry?: RetryConfig; // Request-specific retry config
    responseType?: ResponseType; // How to parse the response
    params?: Record<string, string>; // Query parameters
    }

    The client automatically parses responses based on the Content-Type header:

    // JSON response (Content-Type: application/json)
    const data = await api.get('/users'); // Returns parsed object

    // Text response (Content-Type: text/plain)
    const text = await api.get('/status'); // Returns string

    // Binary response (Content-Type: application/octet-stream)
    const blob = await api.get('/download'); // Returns Blob

    You can specify the response type explicitly:

    // Force JSON parsing
    const data = await api.get('/data', { responseType: 'json' });

    // Force text parsing
    const text = await api.get('/data', { responseType: 'text' });

    // Get as Blob
    const blob = await api.get('/file', { responseType: 'blob' });

    // Get as ArrayBuffer
    const buffer = await api.get('/binary', { responseType: 'arrayBuffer' });

    For full control over response handling, use the request method:

    const response = await api.request({
    url: '/users',
    method: 'GET'
    });

    // Access response properties
    console.log(response.status); // HTTP status code
    console.log(response.statusText); // HTTP status text
    console.log(response.headers); // Response headers

    // Get raw response data
    const rawData = response.getRawData();
    const parsedData = await response.getData();

    The client provides comprehensive error handling with detailed error information:

    import { ApiError, ERROR_CODES } from '@x-common/fetch-client';

    try {
    const data = await api.get('/protected');
    } catch (error) {
    if (error instanceof ApiError) {
    console.log('Error Code:', error.code);
    console.log('HTTP Status:', error.status);
    console.log('Message:', error.message);
    console.log('Request URL:', error.request.url);

    // Handle specific error types
    switch (error.code) {
    case ERROR_CODES.NETWORK_ERROR:
    console.log('Network connection failed');
    break;
    case ERROR_CODES.TIMEOUT_ERROR:
    console.log('Request timed out');
    break;
    case ERROR_CODES.HTTP_ERROR:
    console.log('Server returned error status');
    break;
    case ERROR_CODES.PARSE_ERROR:
    console.log('Failed to parse response');
    break;
    }
    }
    }
    • NETWORK_ERROR: Network connection issues
    • TIMEOUT_ERROR: Request exceeded timeout limit
    • HTTP_ERROR: Server returned error status (4xx, 5xx)
    • PARSE_ERROR: Failed to parse response data
    • VALIDATION_ERROR: Request validation failed
    • ABORT_ERROR: Request was cancelled
    • UNKNOWN_ERROR: Unexpected error occurred

    Interceptors allow you to transform requests and responses globally:

    // Add authentication to all requests
    api.interceptors.request.use((request) => {
    const token = localStorage.getItem('authToken');
    if (token) {
    request.headers.set('Authorization', `Bearer ${token}`);
    }
    return request;
    });

    // Log all outgoing requests
    api.interceptors.request.use((request) => {
    console.log(`Sending ${request.method} request to ${request.url}`);
    return request;
    });

    // Modify request data
    api.interceptors.request.use((request) => {
    if (request.method === 'POST' && request.body) {
    // Add timestamp to all POST requests
    const data = JSON.parse(request.body as string);
    data.timestamp = new Date().toISOString();
    request.body = JSON.stringify(data);
    }
    return request;
    });
    // Handle authentication errors globally
    api.interceptors.response.use((response) => {
    if (response.status === 401) {
    // Redirect to login or refresh token
    window.location.href = '/login';
    }
    return response;
    });

    // Log all responses
    api.interceptors.response.use((response) => {
    console.log(`Received ${response.status} response from ${response.request.url}`);
    return response;
    });

    // Transform response data
    api.interceptors.response.use((response) => {
    // Add metadata to all responses
    const originalGetData = response.getData.bind(response);
    response.getData = async () => {
    const data = await originalGetData();
    return {
    data,
    requestTime: response.request.timestamp,
    responseTime: Date.now()
    };
    };
    return response;
    });

    Configure automatic retries for failed requests:

    const api = new Client({
    retry: {
    maxRetries: 3, // Maximum number of retry attempts
    delay: 1000, // Initial delay between retries (ms)
    shouldRetry: [408, 429, 500, 502, 503, 504] // Status codes to retry
    }
    });
    const api = new Client({
    retry: {
    maxRetries: 5,
    delay: 2000,
    // Custom retry logic
    shouldRetry: (error) => {
    // Retry on network errors
    if (error.code === ERROR_CODES.NETWORK_ERROR) {
    return true;
    }
    // Retry on specific HTTP status codes
    if (error.status && [408, 429, 500, 502, 503, 504].includes(error.status)) {
    return true;
    }
    return false;
    }
    }
    });
    // Override retry config for specific requests
    const data = await api.get('/critical-data', {
    retry: {
    maxRetries: 10,
    delay: 5000
    }
    });

    Create different client instances for different APIs:

    // Main API client
    const mainApi = new Client({
    baseURL: 'https://api.myapp.com',
    headers: { 'Authorization': 'Bearer main-token' }
    });

    // Analytics API client
    const analyticsApi = new Client({
    baseURL: 'https://analytics.myapp.com',
    headers: { 'X-API-Key': 'analytics-key' },
    timeout: 5000
    });

    // File upload client
    const uploadApi = new Client({
    baseURL: 'https://upload.myapp.com',
    timeout: 60000, // Longer timeout for uploads
    retry: { maxRetries: 1 } // Fewer retries for uploads
    });

    Update client configuration at runtime:

    const api = new Client();

    // Set headers dynamically
    api.setHeader('Authorization', `Bearer ${newToken}`);
    api.setHeader('X-Client-Version', '2.0.0');

    // Remove headers
    api.removeHeader('Authorization');

    // Get current configuration
    const config = api.getConfig();
    console.log(config.baseURL, config.headers);

    Handle file uploads with progress tracking:

    // Single file upload
    const fileInput = document.querySelector('input[type="file"]');
    const file = fileInput.files[0];

    const formData = new FormData();
    formData.append('file', file);
    formData.append('description', 'Profile picture');

    const result = await api.post('/upload', formData, {
    timeout: 60000, // 60 second timeout for uploads
    headers: {
    // Don't set Content-Type - let browser set it with boundary
    }
    });

    // Multiple file upload
    const files = Array.from(fileInput.files);
    const formData = new FormData();

    files.forEach((file, index) => {
    formData.append(`file${index}`, file);
    });

    const result = await api.post('/upload-multiple', formData);

    Manage request timeouts to prevent long-running requests:

    // Global timeout configuration
    const api = new Client({
    baseURL: 'https://api.example.com',
    timeout: 10000 // 10 seconds timeout for all requests
    });

    // Per-request timeout override
    try {
    const result = await api.get('/long-running-request', {
    timeout: 30000 // 30 seconds timeout for this specific request
    });
    } catch (error) {
    if (error.code === ERROR_CODES.TIMEOUT_ERROR) {
    console.log('Request timed out');
    }
    }

    // Different timeouts for different operations
    const quickData = await api.get('/quick-data', { timeout: 5000 });
    const uploadResult = await api.post('/upload', formData, { timeout: 60000 });

    The library is built with TypeScript and provides excellent type safety:

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

    interface CreateUserRequest {
    name: string;
    email: string;
    }

    // Typed request and response
    const newUser = await api.post<User>('/users', {
    name: 'John Doe',
    email: 'john@example.com'
    } as CreateUserRequest);

    // newUser is now typed as User
    console.log(newUser.id, newUser.name);
    interface ApiErrorResponse {
    code: string;
    message: string;
    details?: Record<string, string>;
    }

    try {
    await api.post('/users', userData);
    } catch (error) {
    if (error instanceof ApiError && error.response) {
    const errorData = await error.response.getData<ApiErrorResponse>();
    console.log('Error code:', errorData.code);
    console.log('Error message:', errorData.message);
    }
    }

    Create a typed wrapper for your API:

    class MyApiClient {
    private client: Client;

    constructor(baseURL: string, token: string) {
    this.client = new Client({
    baseURL,
    headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
    }
    });
    }

    async getUsers(): Promise<User[]> {
    return this.client.get<User[]>('/users');
    }

    async createUser(userData: CreateUserRequest): Promise<User> {
    return this.client.post<User>('/users', userData);
    }

    async updateUser(id: number, userData: Partial<User>): Promise<User> {
    return this.client.patch<User>(`/users/${id}`, userData);
    }

    async deleteUser(id: number): Promise<void> {
    await this.client.delete(`/users/${id}`);
    }
    }
    import React, { useEffect, useState } from 'react';
    import { Client, ApiError } from '@x-common/fetch-client';

    const api = new Client({
    baseURL: 'https://api.example.com'
    });

    function UserList() {
    const [users, setUsers] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
    async function fetchUsers() {
    try {
    setLoading(true);
    const userData = await api.get('/users');
    setUsers(userData);
    } catch (err) {
    if (err instanceof ApiError) {
    setError(`Failed to load users: ${err.message}`);
    } else {
    setError('An unexpected error occurred');
    }
    } finally {
    setLoading(false);
    }
    }

    fetchUsers();
    }, []);

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

    return (
    <ul>
    {users.map(user => (
    <li key={user.id}>{user.name}</li>
    ))}
    </ul>
    );
    }
    import { Client } from '@x-common/fetch-client';

    // External API client
    const externalApi = new Client({
    baseURL: 'https://external-api.com',
    headers: {
    'X-API-Key': process.env.EXTERNAL_API_KEY
    },
    timeout: 10000
    });

    // Express route handler
    app.get('/api/external-data', async (req, res) => {
    try {
    const data = await externalApi.get('/data', {
    params: {
    category: req.query.category,
    limit: req.query.limit || 10
    }
    });

    res.json(data);
    } catch (error) {
    if (error instanceof ApiError) {
    res.status(error.status || 500).json({
    error: error.message,
    code: error.code
    });
    } else {
    res.status(500).json({
    error: 'Internal server error'
    });
    }
    }
    });
    import { Client } from '@x-common/fetch-client';

    // Mock client for testing
    const mockApi = new Client();

    // Mock successful response
    mockApi.interceptors.response.use((response) => {
    if (response.request.url.includes('/users')) {
    // Return mock data
    const mockUsers = [
    { id: 1, name: 'John Doe', email: 'john@example.com' },
    { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
    ];

    response.getData = async () => mockUsers;
    }
    return response;
    });

    // Test your code
    describe('User Service', () => {
    it('should fetch users', async () => {
    const users = await mockApi.get('/users');
    expect(users).toHaveLength(2);
    expect(users[0].name).toBe('John Doe');
    });
    });
    • Node.js: 16.0.0 or higher
    • TypeScript: 5.0 or higher (for development)
    • Browsers: All modern browsers with fetch support

    All public APIs are exported from the main entry point, allowing for clean imports:

    // Import the main client
    import { Client } from '@x-common/fetch-client';

    // Import specific classes and utilities
    import {
    Client,
    ApiError,
    ApiRequest,
    ApiResponse,
    Interceptor
    } from '@x-common/fetch-client';

    // Import types
    import type {
    RequestConfig,
    ClientConfig,
    ResponseType,
    HttpMethod
    } from '@x-common/fetch-client';

    // Import constants
    import {
    HTTP_STATUS,
    ERROR_CODES,
    CONTENT_TYPES
    } from '@x-common/fetch-client';
    • Client: Main HTTP client class for making requests
    • ApiError: Enhanced error class with detailed error information
    • ApiRequest: Request wrapper with additional metadata
    • ApiResponse: Response wrapper with parsing capabilities
    • Interceptor: Middleware system for request/response processing
    • Body: Request body types (string, FormData, Blob, etc.)
    • HttpMethod: HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD)
    • ResponseType: Response parsing types (json, text, blob, arrayBuffer)
    • RetryConfig: Retry logic configuration
    • RequestConfig: Individual request configuration options
    • ClientConfig: Client-wide configuration options
    • RequestOptions: Simplified request options interface
    • ErrorCode: Error categorization codes
    • HTTP_STATUS: Common HTTP status codes
    • ERROR_CODES: Standardized error codes for consistent error handling
    • CONTENT_TYPES: Content-type parsing patterns
    • DEFAULT_RETRY_STATUSES: Default HTTP status codes that trigger retries
    • RETRY_DEFAULTS: Default retry configuration
    • DEFAULT_RESPONSE_TYPE: Default response parsing mode

    We welcome contributions! Please see our contributing guidelines for details on:

    • Code style and conventions
    • Testing requirements
    • Pull request process
    • Issue reporting

    This project is licensed under the MIT License. See the LICENSE file for details.