+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 120 of 354

🚀 Axios with TypeScript: Advanced HTTP Client

Master Axios for robust HTTP communication with type-safe interceptors, automatic retries, and enterprise-ready patterns 🎯

🚀Intermediate
20 min read

Prerequisites

  • Understanding of HTTP protocols and REST APIs 📝
  • Experience with async/await and Promise patterns ⚡
  • Familiarity with TypeScript generics and interfaces 💻

What you'll learn

  • Master Axios for advanced HTTP communication patterns 🎯
  • Implement type-safe request/response interceptors and transformers 🏗️
  • Build resilient HTTP clients with automatic retries and caching 🐛
  • Create enterprise-ready API abstractions and monitoring ✨

🎯 Introduction

Welcome to the powerful world of Axios! 🚀 If the Fetch API is like a reliable sedan, then Axios is the luxury SUV of HTTP clients - feature-packed, battle-tested, and ready for enterprise-scale applications!

Axios brings powerful features like automatic request/response transformations, interceptors, request cancellation, built-in error handling, and extensive configuration options. Combined with TypeScript’s type safety, you’ll build robust, maintainable HTTP clients that can handle anything from simple API calls to complex enterprise integrations.

By the end of this tutorial, you’ll be an Axios expert, capable of building production-ready HTTP infrastructure that scales with your application’s needs. Let’s dive into the advanced world of HTTP communication! 🌊

📚 Understanding Axios

🤔 What Makes Axios Special?

Axios is a Promise-based HTTP library that provides a rich feature set for making HTTP requests. It’s built on top of the browser’s XMLHttpRequest and Node.js’s http module, offering a consistent API across platforms.

// 🌟 Basic Axios setup with TypeScript
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';

// 📦 Type definitions
interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'moderator';
  isActive: boolean;
  createdAt: string;
  updatedAt: string;
}

interface ApiResponse<T> {
  data: T;
  message: string;
  status: 'success' | 'error';
  timestamp: string;
}

// 🎯 Basic Axios instance with TypeScript
const api: AxiosInstance = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  },
});

// 🚀 Simple GET request with type safety
const getUser = async (id: number): Promise<User> => {
  console.log(`📡 Fetching user ${id}...`);
  
  const response: AxiosResponse<ApiResponse<User>> = await api.get(`/users/${id}`);
  
  console.log('✅ User fetched successfully:', response.data.data);
  return response.data.data;
};

// 📤 POST request with typed payload
const createUser = async (userData: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<User> => {
  console.log('👤 Creating new user...');
  
  const response: AxiosResponse<ApiResponse<User>> = await api.post('/users', userData);
  
  console.log('✅ User created successfully:', response.data.data);
  return response.data.data;
};

// 🎮 Usage example
const exampleUsage = async (): Promise<void> => {
  try {
    // 🔍 Fetch user
    const user = await getUser(123);
    
    // 👤 Create new user
    const newUser = await createUser({
      name: 'Jane Doe',
      email: '[email protected]',
      role: 'user',
      isActive: true,
    });
    
    console.log('🎉 All operations completed successfully!');
  } catch (error) {
    console.error('💥 Operation failed:', error);
  }
};

💡 Key Advantages Over Fetch

  • 🔧 Rich Configuration: Extensive options for timeouts, headers, and transformations
  • 🛡️ Built-in Error Handling: Automatic error throwing for HTTP error status codes
  • 🔄 Request/Response Interceptors: Middleware pattern for cross-cutting concerns
  • 📦 Request/Response Transformation: Automatic JSON parsing and data transformation
  • 🚫 Request Cancellation: Built-in support for cancelling requests
  • 📊 Upload Progress: Monitor file upload progress out of the box
// 🎨 Advanced Axios configuration
interface ApiClientConfig {
  baseURL: string;
  timeout?: number;
  retries?: number;
  retryDelay?: number;
  enableLogging?: boolean;
  authToken?: string;
}

class TypeSafeAxiosClient {
  private instance: AxiosInstance;
  private config: ApiClientConfig;

  constructor(config: ApiClientConfig) {
    this.config = {
      timeout: 10000,
      retries: 3,
      retryDelay: 1000,
      enableLogging: true,
      ...config,
    };

    this.instance = axios.create({
      baseURL: this.config.baseURL,
      timeout: this.config.timeout,
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        ...(this.config.authToken && {
          'Authorization': `Bearer ${this.config.authToken}`,
        }),
      },
    });

    this.setupInterceptors();
  }

  private setupInterceptors(): void {
    // 📊 Request interceptor for logging and auth
    this.instance.interceptors.request.use(
      (config) => {
        if (this.config.enableLogging) {
          console.log(`🚀 ${config.method?.toUpperCase()} ${config.url}`, {
            params: config.params,
            data: config.data,
          });
        }

        // 🔑 Add request timestamp for timing
        config.metadata = { startTime: Date.now() };
        return config;
      },
      (error) => {
        console.error('💥 Request interceptor error:', error);
        return Promise.reject(error);
      }
    );

    // 📥 Response interceptor for logging and error handling
    this.instance.interceptors.response.use(
      (response) => {
        if (this.config.enableLogging) {
          const duration = Date.now() - response.config.metadata?.startTime;
          console.log(`✅ ${response.config.method?.toUpperCase()} ${response.config.url} (${duration}ms)`, {
            status: response.status,
            data: response.data,
          });
        }
        return response;
      },
      async (error) => {
        const originalRequest = error.config;

        // 🔄 Automatic retry logic
        if (this.shouldRetry(error) && !originalRequest._retry) {
          originalRequest._retry = true;
          originalRequest._retryCount = (originalRequest._retryCount || 0) + 1;

          if (originalRequest._retryCount <= this.config.retries!) {
            console.log(`🔄 Retrying request (${originalRequest._retryCount}/${this.config.retries})...`);
            
            await this.delay(this.config.retryDelay! * originalRequest._retryCount);
            return this.instance(originalRequest);
          }
        }

        // 🚨 Enhanced error logging
        if (this.config.enableLogging) {
          console.error(`❌ ${error.config?.method?.toUpperCase()} ${error.config?.url}`, {
            status: error.response?.status,
            message: error.message,
            data: error.response?.data,
          });
        }

        return Promise.reject(this.enhanceError(error));
      }
    );
  }

  private shouldRetry(error: any): boolean {
    // 🔍 Retry on network errors or 5xx server errors
    return (
      !error.response || // Network error
      error.response.status >= 500 || // Server error
      error.response.status === 429 // Rate limited
    );
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  private enhanceError(error: any): Error {
    const enhancedError = new Error(error.message);
    enhancedError.name = 'AxiosError';
    (enhancedError as any).status = error.response?.status;
    (enhancedError as any).data = error.response?.data;
    (enhancedError as any).config = error.config;
    return enhancedError;
  }

  // 🎯 Type-safe HTTP methods
  async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.instance.get<T>(url, config);
    return response.data;
  }

  async post<T, D = any>(url: string, data?: D, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.instance.post<T>(url, data, config);
    return response.data;
  }

  async put<T, D = any>(url: string, data?: D, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.instance.put<T>(url, data, config);
    return response.data;
  }

  async patch<T, D = any>(url: string, data?: D, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.instance.patch<T>(url, data, config);
    return response.data;
  }

  async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.instance.delete<T>(url, config);
    return response.data;
  }

  // 🔧 Utility methods
  setAuthToken(token: string): void {
    this.instance.defaults.headers.common['Authorization'] = `Bearer ${token}`;
  }

  removeAuthToken(): void {
    delete this.instance.defaults.headers.common['Authorization'];
  }

  getInstance(): AxiosInstance {
    return this.instance;
  }
}

🛠️ Advanced Request Configuration

🔧 Request Transformers and Interceptors

Axios provides powerful mechanisms to transform requests and responses before they’re processed:

// 🎛️ Advanced request/response transformation
interface RequestMetrics {
  startTime: number;
  endTime?: number;
  duration?: number;
  retryCount: number;
  endpoint: string;
  method: string;
}

interface ApiError {
  code: string;
  message: string;
  details?: any;
  timestamp: string;
  requestId?: string;
}

class EnterpriseAxiosClient extends TypeSafeAxiosClient {
  private requestMetrics: Map<string, RequestMetrics> = new Map();

  constructor(config: ApiClientConfig) {
    super(config);
    this.setupAdvancedInterceptors();
  }

  private setupAdvancedInterceptors(): void {
    const instance = this.getInstance();

    // 📊 Advanced request interceptor
    instance.interceptors.request.use(
      (config) => {
        // 🎯 Generate unique request ID
        const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
        config.headers['X-Request-ID'] = requestId;

        // 📈 Track request metrics
        const metrics: RequestMetrics = {
          startTime: Date.now(),
          retryCount: 0,
          endpoint: config.url || '',
          method: config.method?.toUpperCase() || 'GET',
        };
        this.requestMetrics.set(requestId, metrics);

        // 🔄 Request transformation
        if (config.data && typeof config.data === 'object') {
          // 📝 Add metadata to all requests
          config.data = {
            ...config.data,
            _metadata: {
              clientVersion: '1.0.0',
              timestamp: new Date().toISOString(),
              requestId,
            },
          };
        }

        // 🔑 Dynamic authentication
        const token = this.getStoredToken();
        if (token && !config.headers['Authorization']) {
          config.headers['Authorization'] = `Bearer ${token}`;
        }

        return config;
      },
      (error) => {
        console.error('💥 Request preparation failed:', error);
        return Promise.reject(error);
      }
    );

    // 📥 Advanced response interceptor
    instance.interceptors.response.use(
      (response) => {
        const requestId = response.config.headers['X-Request-ID'] as string;
        const metrics = this.requestMetrics.get(requestId);

        if (metrics) {
          metrics.endTime = Date.now();
          metrics.duration = metrics.endTime - metrics.startTime;
          
          console.log(`📊 Request metrics:`, {
            requestId,
            endpoint: metrics.endpoint,
            method: metrics.method,
            duration: `${metrics.duration}ms`,
            status: response.status,
          });

          // 🧹 Cleanup old metrics
          this.requestMetrics.delete(requestId);
        }

        // 🎯 Response transformation
        if (response.data && typeof response.data === 'object') {
          // 📦 Extract nested data if following API response pattern
          if (response.data.data !== undefined) {
            response.data = response.data.data;
          }
        }

        return response;
      },
      async (error) => {
        const requestId = error.config?.headers['X-Request-ID'] as string;
        const metrics = this.requestMetrics.get(requestId);

        if (metrics) {
          metrics.retryCount++;
        }

        // 🔄 Token refresh logic
        if (error.response?.status === 401 && !error.config._tokenRefreshAttempted) {
          try {
            await this.refreshToken();
            error.config._tokenRefreshAttempted = true;
            return instance(error.config);
          } catch (refreshError) {
            console.error('🔑 Token refresh failed:', refreshError);
            this.handleAuthFailure();
          }
        }

        // 🚨 Enhanced error object
        const enhancedError: ApiError = {
          code: error.response?.data?.code || 'UNKNOWN_ERROR',
          message: error.response?.data?.message || error.message,
          details: error.response?.data?.details,
          timestamp: new Date().toISOString(),
          requestId,
        };

        throw enhancedError;
      }
    );
  }

  private getStoredToken(): string | null {
    // Implementation depends on your storage strategy
    return localStorage.getItem('authToken');
  }

  private async refreshToken(): Promise<void> {
    const refreshToken = localStorage.getItem('refreshToken');
    if (!refreshToken) {
      throw new Error('No refresh token available');
    }

    const response = await axios.post('/auth/refresh', {
      refreshToken,
    });

    const { accessToken, refreshToken: newRefreshToken } = response.data;
    
    localStorage.setItem('authToken', accessToken);
    localStorage.setItem('refreshToken', newRefreshToken);
    
    this.setAuthToken(accessToken);
  }

  private handleAuthFailure(): void {
    // Clear stored tokens
    localStorage.removeItem('authToken');
    localStorage.removeItem('refreshToken');
    
    // Redirect to login or emit auth failure event
    window.location.href = '/login';
  }
}

// 🎨 Custom request transformer
const customRequestTransformer = (data: any, headers: any) => {
  // 🔄 Transform data before sending
  if (data && typeof data === 'object') {
    // 📅 Convert dates to ISO strings
    Object.keys(data).forEach(key => {
      if (data[key] instanceof Date) {
        data[key] = data[key].toISOString();
      }
    });
    
    // 🗜️ Compress large payloads
    if (JSON.stringify(data).length > 10000) {
      headers['Content-Encoding'] = 'gzip';
      // Add compression logic here
    }
  }
  
  return JSON.stringify(data);
};

// 📦 Custom response transformer
const customResponseTransformer = (data: any) => {
  try {
    const parsed = JSON.parse(data);
    
    // 📅 Convert ISO strings back to dates
    const dateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/;
    
    function transformDates(obj: any): any {
      if (obj === null || obj === undefined) return obj;
      
      if (typeof obj === 'string' && dateRegex.test(obj)) {
        return new Date(obj);
      }
      
      if (Array.isArray(obj)) {
        return obj.map(transformDates);
      }
      
      if (typeof obj === 'object') {
        const transformed: any = {};
        Object.keys(obj).forEach(key => {
          transformed[key] = transformDates(obj[key]);
        });
        return transformed;
      }
      
      return obj;
    }
    
    return transformDates(parsed);
  } catch (error) {
    return data;
  }
};

// 🚀 Usage with custom transformers
const apiWithTransformers = new EnterpriseAxiosClient({
  baseURL: 'https://api.example.com',
  enableLogging: true,
});

const instance = apiWithTransformers.getInstance();
instance.defaults.transformRequest = [customRequestTransformer];
instance.defaults.transformResponse = [customResponseTransformer];

🚫 Request Cancellation and Timeouts

Handle request cancellation gracefully for better user experience:

// 🛑 Advanced request cancellation patterns
interface CancellableRequest<T> {
  promise: Promise<T>;
  cancel: () => void;
  isCancelled: boolean;
}

class CancellableAxiosClient extends EnterpriseAxiosClient {
  private activeCancelTokens: Map<string, AbortController> = new Map();

  // 🎯 Create cancellable request
  createCancellableRequest<T>(
    requestFn: (signal: AbortSignal) => Promise<T>,
    requestId?: string
  ): CancellableRequest<T> {
    const id = requestId || `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    const controller = new AbortController();
    
    this.activeCancelTokens.set(id, controller);

    const promise = requestFn(controller.signal)
      .finally(() => {
        this.activeCancelTokens.delete(id);
      });

    return {
      promise,
      cancel: () => {
        controller.abort();
        this.activeCancelTokens.delete(id);
      },
      get isCancelled() {
        return controller.signal.aborted;
      },
    };
  }

  // 📊 Cancellable requests with timeout and retry
  async getCancellable<T>(
    url: string,
    options: {
      timeout?: number;
      retries?: number;
      requestId?: string;
      onProgress?: (progress: number) => void;
    } = {}
  ): CancellableRequest<T> {
    const { timeout = 10000, retries = 3, requestId, onProgress } = options;

    return this.createCancellableRequest(async (signal) => {
      let attempt = 0;
      let lastError: any;

      while (attempt <= retries) {
        try {
          console.log(`🔄 Attempt ${attempt + 1}/${retries + 1} for ${url}`);
          
          if (onProgress) {
            onProgress((attempt / (retries + 1)) * 100);
          }

          const response = await this.getInstance().get<T>(url, {
            signal,
            timeout,
            onDownloadProgress: (progressEvent) => {
              if (onProgress && progressEvent.total) {
                const progress = (progressEvent.loaded / progressEvent.total) * 100;
                onProgress(progress);
              }
            },
          });

          if (onProgress) {
            onProgress(100);
          }

          return response.data;
        } catch (error: any) {
          lastError = error;
          
          if (signal.aborted) {
            throw new Error('Request was cancelled');
          }

          if (attempt === retries || !this.shouldRetryError(error)) {
            break;
          }

          const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
          console.log(`⏱️ Retrying in ${delay}ms...`);
          await this.delay(delay);
          attempt++;
        }
      }

      throw lastError;
    }, requestId);
  }

  // 📤 Cancellable file upload with progress
  uploadFileCancellable(
    url: string,
    file: File,
    options: {
      onProgress?: (progress: number) => void;
      onCancel?: () => void;
      requestId?: string;
    } = {}
  ): CancellableRequest<any> {
    const { onProgress, onCancel, requestId } = options;

    return this.createCancellableRequest(async (signal) => {
      const formData = new FormData();
      formData.append('file', file);

      signal.addEventListener('abort', () => {
        console.log('📁 File upload cancelled');
        onCancel?.();
      });

      const response = await this.getInstance().post(url, formData, {
        signal,
        headers: {
          'Content-Type': 'multipart/form-data',
        },
        onUploadProgress: (progressEvent) => {
          if (onProgress && progressEvent.total) {
            const progress = (progressEvent.loaded / progressEvent.total) * 100;
            onProgress(Math.round(progress));
          }
        },
      });

      return response.data;
    }, requestId);
  }

  // 🧹 Cancel all active requests
  cancelAllRequests(): void {
    console.log(`🛑 Cancelling ${this.activeCancelTokens.size} active requests`);
    
    this.activeCancelTokens.forEach((controller, requestId) => {
      console.log(`❌ Cancelling request: ${requestId}`);
      controller.abort();
    });
    
    this.activeCancelTokens.clear();
  }

  // 🔍 Get active request count
  getActiveRequestCount(): number {
    return this.activeCancelTokens.size;
  }

  private shouldRetryError(error: any): boolean {
    return (
      !error.response || // Network error
      error.response.status >= 500 || // Server error
      error.response.status === 429 // Rate limited
    );
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// 🎮 Usage examples
const cancellableClient = new CancellableAxiosClient({
  baseURL: 'https://api.example.com',
  enableLogging: true,
});

// 📊 Cancellable data fetching
const fetchUserData = async (userId: number): Promise<void> => {
  const request = cancellableClient.getCancellable<User>(`/users/${userId}`, {
    timeout: 5000,
    retries: 2,
    onProgress: (progress) => {
      console.log(`📊 Progress: ${progress}%`);
    },
  });

  // 🛑 Cancel after 3 seconds if needed
  setTimeout(() => {
    if (!request.isCancelled) {
      console.log('⏰ Cancelling request due to timeout');
      request.cancel();
    }
  }, 3000);

  try {
    const user = await request.promise;
    console.log('✅ User fetched:', user);
  } catch (error) {
    console.error('❌ Failed to fetch user:', error);
  }
};

// 📁 Cancellable file upload
const uploadFile = async (file: File): Promise<void> => {
  const upload = cancellableClient.uploadFileCancellable('/upload', file, {
    onProgress: (progress) => {
      console.log(`📁 Upload progress: ${progress}%`);
      // Update UI progress bar here
    },
    onCancel: () => {
      console.log('🛑 Upload cancelled by user');
    },
  });

  // 🎛️ User can cancel upload
  document.getElementById('cancelBtn')?.addEventListener('click', () => {
    upload.cancel();
  });

  try {
    const result = await upload.promise;
    console.log('✅ File uploaded successfully:', result);
  } catch (error) {
    console.error('❌ Upload failed:', error);
  }
};

🎯 Type-Safe API Client Architecture

🏗️ Resource-Based Client Pattern

Create a scalable architecture for managing different API resources:

// 🎨 Generic resource client with full CRUD operations
interface ResourceClient<T, TCreate = Omit<T, 'id'>, TUpdate = Partial<TCreate>> {
  getAll(params?: Record<string, any>): Promise<T[]>;
  getById(id: string | number): Promise<T>;
  create(data: TCreate): Promise<T>;
  update(id: string | number, data: TUpdate): Promise<T>;
  delete(id: string | number): Promise<void>;
  search(query: string, filters?: Record<string, any>): Promise<T[]>;
}

abstract class BaseResourceClient<T, TCreate = Omit<T, 'id'>, TUpdate = Partial<TCreate>>
  implements ResourceClient<T, TCreate, TUpdate> {
  
  constructor(
    protected client: CancellableAxiosClient,
    protected endpoint: string
  ) {}

  async getAll(params: Record<string, any> = {}): Promise<T[]> {
    console.log(`📋 Fetching all ${this.endpoint}...`);
    
    const response = await this.client.get<{ items: T[]; total: number }>(
      this.endpoint,
      { params }
    );
    
    return response.items;
  }

  async getById(id: string | number): Promise<T> {
    console.log(`🔍 Fetching ${this.endpoint}/${id}...`);
    return this.client.get<T>(`${this.endpoint}/${id}`);
  }

  async create(data: TCreate): Promise<T> {
    console.log(`✨ Creating new ${this.endpoint}...`);
    return this.client.post<T>(this.endpoint, data);
  }

  async update(id: string | number, data: TUpdate): Promise<T> {
    console.log(`🔄 Updating ${this.endpoint}/${id}...`);
    return this.client.patch<T>(`${this.endpoint}/${id}`, data);
  }

  async delete(id: string | number): Promise<void> {
    console.log(`🗑️ Deleting ${this.endpoint}/${id}...`);
    await this.client.delete(`${this.endpoint}/${id}`);
  }

  async search(query: string, filters: Record<string, any> = {}): Promise<T[]> {
    console.log(`🔍 Searching ${this.endpoint}: "${query}"`);
    
    const params = { q: query, ...filters };
    const response = await this.client.get<{ items: T[] }>(`${this.endpoint}/search`, {
      params,
    });
    
    return response.items;
  }

  // 🛑 Create cancellable operations
  protected createCancellableOperation<TResult>(
    operation: (signal: AbortSignal) => Promise<TResult>,
    requestId?: string
  ) {
    return this.client.createCancellableRequest(operation, requestId);
  }
}

// 👤 User resource client
interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'moderator';
  isActive: boolean;
  lastLogin?: string;
  createdAt: string;
  updatedAt: string;
}

interface CreateUser {
  name: string;
  email: string;
  password: string;
  role?: 'user' | 'moderator';
}

interface UpdateUser {
  name?: string;
  email?: string;
  role?: 'admin' | 'user' | 'moderator';
  isActive?: boolean;
}

class UserClient extends BaseResourceClient<User, CreateUser, UpdateUser> {
  constructor(client: CancellableAxiosClient) {
    super(client, '/users');
  }

  // 🔍 User-specific methods
  async getByEmail(email: string): Promise<User | null> {
    try {
      return await this.client.get<User>(`/users/by-email/${encodeURIComponent(email)}`);
    } catch (error: any) {
      if (error.status === 404) {
        return null;
      }
      throw error;
    }
  }

  async changePassword(id: number, currentPassword: string, newPassword: string): Promise<void> {
    console.log(`🔐 Changing password for user ${id}...`);
    
    await this.client.post(`/users/${id}/change-password`, {
      currentPassword,
      newPassword,
    });
  }

  async toggleStatus(id: number): Promise<User> {
    console.log(`🔄 Toggling status for user ${id}...`);
    return this.client.post<User>(`/users/${id}/toggle-status`);
  }

  async getUserStats(id: number): Promise<{
    loginCount: number;
    lastActive: string;
    createdPosts: number;
  }> {
    console.log(`📊 Fetching stats for user ${id}...`);
    return this.client.get(`/users/${id}/stats`);
  }

  // 🛑 Cancellable user search with real-time updates
  searchUsersLive(
    query: string,
    onUpdate: (users: User[]) => void,
    requestId?: string
  ) {
    return this.createCancellableOperation(async (signal) => {
      const eventSource = new EventSource(`/users/search/live?q=${encodeURIComponent(query)}`);
      
      signal.addEventListener('abort', () => {
        eventSource.close();
      });

      return new Promise<User[]>((resolve, reject) => {
        eventSource.onmessage = (event) => {
          try {
            const users: User[] = JSON.parse(event.data);
            onUpdate(users);
          } catch (error) {
            reject(error);
          }
        };

        eventSource.onerror = (error) => {
          eventSource.close();
          reject(error);
        };

        // Complete after 30 seconds
        setTimeout(() => {
          eventSource.close();
          resolve([]);
        }, 30000);
      });
    }, requestId);
  }
}

// 📝 Post resource client
interface Post {
  id: number;
  title: string;
  content: string;
  excerpt: string;
  authorId: number;
  author?: User;
  tags: string[];
  published: boolean;
  publishedAt?: string;
  createdAt: string;
  updatedAt: string;
}

interface CreatePost {
  title: string;
  content: string;
  authorId: number;
  tags?: string[];
  published?: boolean;
}

class PostClient extends BaseResourceClient<Post, CreatePost> {
  constructor(client: CancellableAxiosClient) {
    super(client, '/posts');
  }

  async getByAuthor(authorId: number, includeUnpublished = false): Promise<Post[]> {
    console.log(`📝 Fetching posts by author ${authorId}...`);
    
    const params = { authorId, includeUnpublished };
    return this.client.get<Post[]>('/posts/by-author', { params });
  }

  async publish(id: number): Promise<Post> {
    console.log(`📢 Publishing post ${id}...`);
    return this.client.post<Post>(`/posts/${id}/publish`);
  }

  async unpublish(id: number): Promise<Post> {
    console.log(`📝 Unpublishing post ${id}...`);
    return this.client.post<Post>(`/posts/${id}/unpublish`);
  }

  async getByTags(tags: string[]): Promise<Post[]> {
    console.log(`🏷️ Fetching posts by tags: ${tags.join(', ')}`);
    
    const params = { tags: tags.join(',') };
    return this.client.get<Post[]>('/posts/by-tags', { params });
  }

  async getPopular(limit = 10): Promise<Post[]> {
    console.log(`🔥 Fetching ${limit} popular posts...`);
    return this.client.get<Post[]>(`/posts/popular?limit=${limit}`);
  }

  // 📊 Upload post images with progress
  uploadImage(
    postId: number,
    file: File,
    onProgress?: (progress: number) => void
  ) {
    return this.client.uploadFileCancellable(`/posts/${postId}/images`, file, {
      onProgress,
      requestId: `upload_post_image_${postId}`,
    });
  }
}

// 🏗️ Main API service
class ApiService {
  private client: CancellableAxiosClient;
  
  public users: UserClient;
  public posts: PostClient;

  constructor(baseURL: string, authToken?: string) {
    this.client = new CancellableAxiosClient({
      baseURL,
      authToken,
      enableLogging: true,
      retries: 3,
      timeout: 15000,
    });

    this.setupGlobalInterceptors();
    
    // 🎯 Initialize resource clients
    this.users = new UserClient(this.client);
    this.posts = new PostClient(this.client);
  }

  private setupGlobalInterceptors(): void {
    const instance = this.client.getInstance();

    // 📊 Global request metrics
    instance.interceptors.request.use((config) => {
      console.log(`🌐 API Request: ${config.method?.toUpperCase()} ${config.url}`);
      return config;
    });

    // 🚨 Global error handling
    instance.interceptors.response.use(
      (response) => response,
      (error) => {
        if (error.response?.status === 403) {
          console.error('🚫 Access forbidden - insufficient permissions');
          // Show user-friendly error message
        } else if (error.response?.status >= 500) {
          console.error('🔥 Server error - please try again later');
          // Show server error message
        }
        return Promise.reject(error);
      }
    );
  }

  // 🔑 Authentication methods
  async login(email: string, password: string): Promise<{ user: User; token: string }> {
    console.log('🔐 Logging in...');
    
    const response = await this.client.post<{ user: User; token: string }>('/auth/login', {
      email,
      password,
    });

    this.client.setAuthToken(response.token);
    console.log('✅ Login successful');
    
    return response;
  }

  async logout(): Promise<void> {
    console.log('👋 Logging out...');
    
    try {
      await this.client.post('/auth/logout');
    } catch (error) {
      console.warn('⚠️ Logout request failed, but clearing local session');
    }
    
    this.client.removeAuthToken();
    console.log('✅ Logout successful');
  }

  // 🛑 Utility methods
  cancelAllRequests(): void {
    this.client.cancelAllRequests();
  }

  getActiveRequestCount(): number {
    return this.client.getActiveRequestCount();
  }

  // 📊 Health check
  async healthCheck(): Promise<{ status: string; timestamp: string }> {
    return this.client.get<{ status: string; timestamp: string }>('/health');
  }
}

// 🚀 Usage example
const api = new ApiService('https://api.myapp.com');

// 🎮 Complete workflow example
const blogWorkflow = async (): Promise<void> => {
  try {
    // 🔐 Login
    const { user, token } = await api.login('[email protected]', 'password123');
    console.log(`👋 Welcome, ${user.name}!`);

    // 👤 Get user stats
    const stats = await api.users.getUserStats(user.id);
    console.log('📊 User stats:', stats);

    // 📝 Create a new post
    const newPost = await api.posts.create({
      title: 'My TypeScript Journey',
      content: 'TypeScript has revolutionized my development workflow...',
      authorId: user.id,
      tags: ['typescript', 'programming', 'web-development'],
      published: false,
    });

    // 📁 Upload an image for the post
    const fileInput = document.getElementById('file-input') as HTMLInputElement;
    if (fileInput.files && fileInput.files[0]) {
      const imageUpload = api.posts.uploadImage(
        newPost.id,
        fileInput.files[0],
        (progress) => {
          console.log(`📊 Upload progress: ${progress}%`);
        }
      );

      const uploadResult = await imageUpload.promise;
      console.log('✅ Image uploaded:', uploadResult);
    }

    // 📢 Publish the post
    const publishedPost = await api.posts.publish(newPost.id);
    console.log('📢 Post published:', publishedPost);

    console.log('🎉 Blog workflow completed successfully!');
  } catch (error) {
    console.error('💥 Workflow failed:', error);
  }
};

🧪 Testing Axios Clients

🎭 Advanced Mocking and Testing Strategies

Create comprehensive tests for your Axios-based HTTP clients:

// 🧪 Advanced Axios testing utilities
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';

interface TestScenario {
  name: string;
  setup: (mock: MockAdapter) => void;
  execute: () => Promise<any>;
  expect: (result: any, error?: any) => void;
}

class AxiosTestHelper {
  private mock: MockAdapter;
  private originalAxios: typeof axios;

  constructor() {
    this.mock = new MockAdapter(axios);
    this.originalAxios = axios;
  }

  setup(): void {
    this.mock.reset();
  }

  teardown(): void {
    this.mock.restore();
  }

  // 🎭 Mock response patterns
  mockSuccessResponse<T>(
    method: 'get' | 'post' | 'put' | 'patch' | 'delete',
    url: string | RegExp,
    responseData: T,
    status = 200,
    headers = {}
  ): void {
    this.mock[`on${method.charAt(0).toUpperCase() + method.slice(1)}`](url)
      .reply(status, responseData, headers);
  }

  mockErrorResponse(
    method: 'get' | 'post' | 'put' | 'patch' | 'delete',
    url: string | RegExp,
    status: number,
    errorData?: any
  ): void {
    this.mock[`on${method.charAt(0).toUpperCase() + method.slice(1)}`](url)
      .reply(status, errorData);
  }

  mockNetworkError(
    method: 'get' | 'post' | 'put' | 'patch' | 'delete',
    url: string | RegExp
  ): void {
    this.mock[`on${method.charAt(0).toUpperCase() + method.slice(1)}`](url)
      .networkError();
  }

  mockTimeout(
    method: 'get' | 'post' | 'put' | 'patch' | 'delete',
    url: string | RegExp
  ): void {
    this.mock[`on${method.charAt(0).toUpperCase() + method.slice(1)}`](url)
      .timeout();
  }

  // 🔄 Mock delayed responses
  mockDelayedResponse<T>(
    method: 'get' | 'post' | 'put' | 'patch' | 'delete',
    url: string | RegExp,
    responseData: T,
    delay: number,
    status = 200
  ): void {
    this.mock[`on${method.charAt(0).toUpperCase() + method.slice(1)}`](url)
      .reply(() => {
        return new Promise(resolve => {
          setTimeout(() => {
            resolve([status, responseData]);
          }, delay);
        });
      });
  }

  // 📊 Get request history
  getRequestHistory(): Array<{
    method: string;
    url: string;
    data?: any;
    params?: any;
  }> {
    return this.mock.history.get.concat(
      this.mock.history.post,
      this.mock.history.put,
      this.mock.history.patch,
      this.mock.history.delete
    ).map(req => ({
      method: req.method?.toUpperCase() || 'UNKNOWN',
      url: req.url || '',
      data: req.data ? JSON.parse(req.data) : undefined,
      params: req.params,
    }));
  }

  // 🎯 Run test scenarios
  async runScenarios(scenarios: TestScenario[]): Promise<void> {
    for (const scenario of scenarios) {
      console.log(`🧪 Running scenario: ${scenario.name}`);
      
      this.mock.reset();
      scenario.setup(this.mock);
      
      try {
        const result = await scenario.execute();
        scenario.expect(result);
        console.log(`✅ Scenario passed: ${scenario.name}`);
      } catch (error) {
        scenario.expect(null, error);
        console.log(`✅ Scenario passed (expected error): ${scenario.name}`);
      }
    }
  }
}

// 🧪 Comprehensive test suite
describe('ApiService', () => {
  let testHelper: AxiosTestHelper;
  let apiService: ApiService;

  beforeEach(() => {
    testHelper = new AxiosTestHelper();
    testHelper.setup();
    apiService = new ApiService('https://api.test.com');
  });

  afterEach(() => {
    testHelper.teardown();
  });

  describe('User Operations', () => {
    it('should fetch user by ID with proper error handling', async () => {
      const mockUser: User = {
        id: 1,
        name: 'John Doe',
        email: '[email protected]',
        role: 'user',
        isActive: true,
        createdAt: '2023-01-01T00:00:00Z',
        updatedAt: '2023-01-01T00:00:00Z',
      };

      testHelper.mockSuccessResponse('get', '/users/1', mockUser);

      const user = await apiService.users.getById(1);

      expect(user).toEqual(mockUser);
      
      const history = testHelper.getRequestHistory();
      expect(history).toHaveLength(1);
      expect(history[0].url).toBe('/users/1');
    });

    it('should handle user not found gracefully', async () => {
      testHelper.mockErrorResponse('get', '/users/999', 404, {
        code: 'USER_NOT_FOUND',
        message: 'User not found',
      });

      await expect(apiService.users.getById(999)).rejects.toMatchObject({
        code: 'USER_NOT_FOUND',
        message: 'User not found',
      });
    });

    it('should retry on server errors', async () => {
      const mockUser: User = {
        id: 1,
        name: 'John Doe',
        email: '[email protected]',
        role: 'user',
        isActive: true,
        createdAt: '2023-01-01T00:00:00Z',
        updatedAt: '2023-01-01T00:00:00Z',
      };

      // First request fails with 500
      testHelper.mockErrorResponse('get', '/users/1', 500);
      // Second request succeeds
      testHelper.mockSuccessResponse('get', '/users/1', mockUser);

      const user = await apiService.users.getById(1);

      expect(user).toEqual(mockUser);
      
      const history = testHelper.getRequestHistory();
      expect(history.length).toBeGreaterThan(1); // Should have retried
    });
  });

  describe('Authentication Flow', () => {
    it('should handle login and token management', async () => {
      const mockLoginResponse = {
        user: {
          id: 1,
          name: 'John Doe',
          email: '[email protected]',
          role: 'user' as const,
          isActive: true,
          createdAt: '2023-01-01T00:00:00Z',
          updatedAt: '2023-01-01T00:00:00Z',
        },
        token: 'mock-jwt-token',
      };

      testHelper.mockSuccessResponse('post', '/auth/login', mockLoginResponse);

      const result = await apiService.login('[email protected]', 'password123');

      expect(result).toEqual(mockLoginResponse);
      
      const history = testHelper.getRequestHistory();
      expect(history[0].data).toEqual({
        email: '[email protected]',
        password: 'password123',
      });
    });

    it('should handle token refresh on 401 errors', async () => {
      const mockUser: User = {
        id: 1,
        name: 'John Doe',
        email: '[email protected]',
        role: 'user',
        isActive: true,
        createdAt: '2023-01-01T00:00:00Z',
        updatedAt: '2023-01-01T00:00:00Z',
      };

      // First request returns 401
      testHelper.mockErrorResponse('get', '/users/1', 401);
      // Token refresh succeeds
      testHelper.mockSuccessResponse('post', '/auth/refresh', {
        accessToken: 'new-token',
        refreshToken: 'new-refresh-token',
      });
      // Retry with new token succeeds
      testHelper.mockSuccessResponse('get', '/users/1', mockUser);

      // Mock localStorage
      Object.defineProperty(window, 'localStorage', {
        value: {
          getItem: jest.fn().mockReturnValue('refresh-token'),
          setItem: jest.fn(),
          removeItem: jest.fn(),
        },
        writable: true,
      });

      const user = await apiService.users.getById(1);

      expect(user).toEqual(mockUser);
    });
  });

  describe('Request Cancellation', () => {
    it('should cancel requests properly', async () => {
      testHelper.mockDelayedResponse('get', '/users/1', { id: 1 }, 5000);

      const cancellableClient = new CancellableAxiosClient({
        baseURL: 'https://api.test.com',
      });

      const request = cancellableClient.getCancellable('/users/1');

      // Cancel after 100ms
      setTimeout(() => {
        request.cancel();
      }, 100);

      await expect(request.promise).rejects.toThrow('Request was cancelled');
      expect(request.isCancelled).toBe(true);
    });
  });

  describe('Error Scenarios', () => {
    const errorScenarios: TestScenario[] = [
      {
        name: 'Network Error',
        setup: (mock) => {
          testHelper.mockNetworkError('get', '/users/1');
        },
        execute: () => apiService.users.getById(1),
        expect: (result, error) => {
          expect(error).toBeDefined();
          expect(error.message).toContain('Network Error');
        },
      },
      {
        name: 'Timeout Error',
        setup: (mock) => {
          testHelper.mockTimeout('get', '/users/1');
        },
        execute: () => apiService.users.getById(1),
        expect: (result, error) => {
          expect(error).toBeDefined();
          expect(error.code).toBe('ECONNABORTED');
        },
      },
      {
        name: 'Server Error with Retry',
        setup: (mock) => {
          // Multiple 500 errors to test retry exhaustion
          testHelper.mockErrorResponse('get', '/users/1', 500);
          testHelper.mockErrorResponse('get', '/users/1', 500);
          testHelper.mockErrorResponse('get', '/users/1', 500);
          testHelper.mockErrorResponse('get', '/users/1', 500);
        },
        execute: () => apiService.users.getById(1),
        expect: (result, error) => {
          expect(error).toBeDefined();
          expect(error.status).toBe(500);
        },
      },
    ];

    it('should handle various error scenarios', async () => {
      await testHelper.runScenarios(errorScenarios);
    });
  });

  describe('Performance and Load Testing', () => {
    it('should handle concurrent requests efficiently', async () => {
      const mockUser: User = {
        id: 1,
        name: 'John Doe',
        email: '[email protected]',
        role: 'user',
        isActive: true,
        createdAt: '2023-01-01T00:00:00Z',
        updatedAt: '2023-01-01T00:00:00Z',
      };

      // Mock multiple user responses
      for (let i = 1; i <= 10; i++) {
        testHelper.mockSuccessResponse('get', `/users/${i}`, {
          ...mockUser,
          id: i,
          name: `User ${i}`,
        });
      }

      const startTime = Date.now();
      
      // Make 10 concurrent requests
      const promises = Array.from({ length: 10 }, (_, i) =>
        apiService.users.getById(i + 1)
      );

      const results = await Promise.all(promises);
      const endTime = Date.now();

      expect(results).toHaveLength(10);
      expect(endTime - startTime).toBeLessThan(1000); // Should complete quickly
      
      const history = testHelper.getRequestHistory();
      expect(history).toHaveLength(10);
    });
  });
});

// 🎭 Integration testing utilities
class ApiIntegrationTest {
  private apiService: ApiService;
  private testData: {
    users: User[];
    posts: Post[];
  } = {
    users: [],
    posts: [],
  };

  constructor(baseURL: string) {
    this.apiService = new ApiService(baseURL);
  }

  async setupTestData(): Promise<void> {
    console.log('🎭 Setting up test data...');
    
    // Create test users
    for (let i = 1; i <= 3; i++) {
      const user = await this.apiService.users.create({
        name: `Test User ${i}`,
        email: `test${i}@example.com`,
        password: 'testpass123',
        role: i === 1 ? 'admin' : 'user',
      });
      this.testData.users.push(user);
    }

    // Create test posts
    for (let i = 1; i <= 5; i++) {
      const post = await this.apiService.posts.create({
        title: `Test Post ${i}`,
        content: `This is test post content ${i}`,
        authorId: this.testData.users[i % 3].id,
        tags: [`tag${i}`, 'test'],
        published: i % 2 === 0,
      });
      this.testData.posts.push(post);
    }

    console.log('✅ Test data setup complete');
  }

  async runFullWorkflowTest(): Promise<void> {
    console.log('🚀 Running full workflow integration test...');

    try {
      // 1. Login
      const { user } = await this.apiService.login(
        this.testData.users[0].email,
        'testpass123'
      );
      console.log('✅ Login successful');

      // 2. Fetch user stats
      const stats = await this.apiService.users.getUserStats(user.id);
      console.log('✅ User stats fetched:', stats);

      // 3. Search posts
      const searchResults = await this.apiService.posts.search('test');
      console.log('✅ Post search completed:', searchResults.length);

      // 4. Create new post
      const newPost = await this.apiService.posts.create({
        title: 'Integration Test Post',
        content: 'This post was created during integration testing',
        authorId: user.id,
        tags: ['integration', 'test'],
        published: false,
      });
      console.log('✅ New post created:', newPost.id);

      // 5. Publish post
      const publishedPost = await this.apiService.posts.publish(newPost.id);
      console.log('✅ Post published:', publishedPost.published);

      // 6. Get posts by author
      const authorPosts = await this.apiService.posts.getByAuthor(user.id);
      console.log('✅ Author posts fetched:', authorPosts.length);

      // 7. Logout
      await this.apiService.logout();
      console.log('✅ Logout successful');

      console.log('🎉 Full workflow integration test completed successfully!');
    } catch (error) {
      console.error('💥 Integration test failed:', error);
      throw error;
    }
  }

  async cleanup(): Promise<void> {
    console.log('🧹 Cleaning up test data...');
    
    try {
      // Delete test posts
      for (const post of this.testData.posts) {
        await this.apiService.posts.delete(post.id);
      }

      // Delete test users
      for (const user of this.testData.users) {
        await this.apiService.users.delete(user.id);
      }

      console.log('✅ Test data cleanup complete');
    } catch (error) {
      console.warn('⚠️ Cleanup partially failed:', error);
    }
  }
}

// 🧪 Run integration tests
const runIntegrationTests = async (): Promise<void> => {
  if (process.env.NODE_ENV === 'test' && process.env.API_BASE_URL) {
    const integrationTest = new ApiIntegrationTest(process.env.API_BASE_URL);
    
    try {
      await integrationTest.setupTestData();
      await integrationTest.runFullWorkflowTest();
    } finally {
      await integrationTest.cleanup();
    }
  }
};

🎯 Conclusion

Congratulations! 🎉 You’ve mastered Axios with TypeScript and built enterprise-grade HTTP clients! You now have the expertise to:

  • 🚀 Master Axios features with type-safe request/response patterns and interceptors
  • 🔧 Build resilient clients with automatic retries, cancellation, and error recovery
  • 🏗️ Create scalable architectures with resource-based patterns and middleware
  • 🧪 Test HTTP infrastructure with comprehensive mocking and integration strategies

Axios combined with TypeScript provides a powerful foundation for building robust, maintainable HTTP communication layers. From simple API calls to complex enterprise integrations, you’re now equipped to handle any HTTP challenge with confidence and style!

Keep exploring advanced patterns, experiment with GraphQL integration, and remember that great HTTP architecture is the backbone of excellent user experiences. The API world is yours to conquer! 🌐✨