← Back to all snippets
JAVASCRIPT

Centralized API Client with Request Interceptors

Create a modular API client in JavaScript that uses interceptors for global error handling, logging, or modifying request/response objects before they are processed by your application, enhancing maintainability.

class ApiClient {
  constructor(baseURL) {
    this.baseURL = baseURL;
    this.requestInterceptors = [];
    this.responseInterceptors = [];
  }

  addRequestInterceptor(interceptor) {
    this.requestInterceptors.push(interceptor);
  }

  addResponseInterceptor(interceptor) {
    this.responseInterceptors.push(interceptor);
  }

  async request(endpoint, options = {}) {
    let url = `${this.baseURL}${endpoint}`;
    let config = { ...options };

    // Apply request interceptors
    for (const interceptor of this.requestInterceptors) {
      config = await interceptor(url, config);
      if (config === null) { // Allow interceptor to block request
          throw new Error('Request blocked by interceptor');
      }
    }

    try {
      const response = await fetch(url, config);
      let processedResponse = response;

      // Apply response interceptors
      for (const interceptor of this.responseInterceptors) {
        processedResponse = await interceptor(processedResponse);
      }
      
      if (!processedResponse.ok) {
          throw new Error(`HTTP error! Status: ${processedResponse.status}`);
      }

      return processedResponse.json();
    } catch (error) {
      // Global error handling or re-throwing after response interceptors
      console.error('API client request failed:', error);
      throw error;
    }
  }
}

// Usage example:
// const apiClient = new ApiClient('https://jsonplaceholder.typicode.com');
//
// // Add a request interceptor for logging and adding headers
// apiClient.addRequestInterceptor(async (url, config) => {
//   console.log(`Sending request to: ${url}`);
//   config.headers = {
//     ...config.headers,
//     'X-Custom-Header': 'Intercepted-Value'
//   };
//   return config;
// });
//
// // Add a request interceptor for potential authentication token refresh (pseudo-code)
// apiClient.addRequestInterceptor(async (url, config) => {
//   // In a real app, check if token is expired and refresh if necessary
//   // const token = localStorage.getItem('authToken');
//   // if (token) {
//   //   config.headers = { ...config.headers, 'Authorization': `Bearer ${token}` };
//   // }
//   return config;
// });
//
// // Add a response interceptor for error logging or specific status code handling
// apiClient.addResponseInterceptor(async (response) => {
//   if (response.status === 401) {
//     console.warn('Unauthorized access - redirecting to login?');
//     // Potentially trigger a redirect or token refresh logic
//   }
//   return response;
// });
//
// apiClient.request('/posts/1')
//   .then(data => console.log('Fetched post:', data))
//   .catch(error => console.error('Failed to fetch post:', error));
How it works: This JavaScript snippet demonstrates how to build a centralized API client with request and response interceptors. Interceptors are functions that can modify requests before they are sent or process responses before they reach the calling code. This pattern allows for global concerns like adding authentication headers, logging, error handling, or token refreshing to be handled uniformly across all API calls, improving code maintainability and separation of concerns in complex web applications.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs