JAVASCRIPT

Robust API Error Handling with Custom JavaScript Errors

Learn to create a reusable JavaScript `fetch` wrapper that gracefully handles various HTTP status codes and API error messages, throwing custom error types for better control.

class ApiError extends Error {
  constructor(message, status, data = null) {
    super(message);
    this.name = 'ApiError';
    this.status = status;
    this.data = data;
    Object.setPrototypeOf(this, ApiError.prototype); // Fix for instanceof check
  }
}

async function fetchData(url, options = {}) {
  try {
    const response = await fetch(url, options);

    if (!response.ok) {
      const errorData = await response.json().catch(() => ({ message: 'Unknown error format' }));
      let errorMessage = errorData.message || `API Error: ${response.status} ${response.statusText}`;

      if (response.status === 401) {
        errorMessage = errorData.message || 'Authentication required.';
        // Potentially redirect to login or refresh token
      } else if (response.status === 403) {
        errorMessage = errorData.message || 'Permission denied.';
      } else if (response.status === 404) {
        errorMessage = errorData.message || 'Resource not found.';
      } else if (response.status >= 500) {
        errorMessage = errorData.message || 'Server error occurred.';
      }

      throw new ApiError(errorMessage, response.status, errorData);
    }

    return await response.json();
  } catch (error) {
    if (error instanceof ApiError) {
      throw error; // Re-throw custom API errors
    }
    // Handle network errors or other unexpected errors
    throw new ApiError(`Network or unexpected error: ${error.message}`, 0, null);
  }
}

// Example Usage:
// fetchData('https://api.example.com/data')
//   .then(data => console.log('Success:', data))
//   .catch(error => {
//     console.error('Caught error:', error.message);
//     if (error instanceof ApiError) {
//       console.error('API Status:', error.status);
//       console.error('API Error Data:', error.data);
//       if (error.status === 401) {
//         // Handle unauthorized specifically
//       }
//     }
//   });

// Example usage with a POST request:
// fetchData('https://api.example.com/items', {
//   method: 'POST',
//   headers: { 'Content-Type': 'application/json' },
//   body: JSON.stringify({ name: 'New Item' })
// }).then(data => console.log('Item created:', data))
//   .catch(error => console.error('Error creating item:', error));
How it works: This snippet provides a robust `fetchData` wrapper around the native `fetch` API. It defines a custom `ApiError` class to carry HTTP status and API-specific error data. The `fetchData` function checks `response.ok` and, if false, attempts to parse the error body, constructing an `ApiError` with a user-friendly message based on common status codes. This allows consuming code to catch and handle different types of API errors specifically, improving user experience and debugging.

Need help integrating this into your project?

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

Hire DigitalCodeLabs