Frontend Development 10 min read

Adapting JavaScript API Modules: From Fetch to Axios with the Adapter Pattern

The article explains how to adapt a JavaScript API module from using the Fetch API to Axios by introducing adapter abstractions, detailing code changes, error handling, and the benefits of refactoring for maintainability and extensibility.

Hujiang Technology
Hujiang Technology
Hujiang Technology
Adapting JavaScript API Modules: From Fetch to Axios with the Adapter Pattern

Even if you haven't read the original article on the importance of JavaScript abstractions for handling network data, you likely already understand that code maintainability and extensibility are crucial, which is why we introduce JavaScript abstractions.

We assume that an abstraction in JavaScript is a module and split a module's lifecycle into three stages: (1) introducing the module, (2) adjusting the module, and (3) removing the module. The previous article focused on the first stage; this one focuses on the second.

Changing modules is a common challenge. Adjusting a module is as important as introducing it for keeping a project maintainable and extensible. The article emphasizes that developers often make non‑optimal changes under deadline pressure.

Techniques for Maintaining Modifications

We start with an API module that encapsulates all configuration and settings related to external API communication.

class API {
  constructor() {
    this.url = 'http://whatever.api/v1/';
  }

  /**
   * API specific data fetching method
   * Checks if an HTTP response status is in the success range
   */
  _handleError(_res) {
    return _res.ok ? _res : Promise.reject(_res.statusText);
  }

  /**
   * Fetch data
   * @return {Promise}
   */
  get(_endpoint) {
    return window.fetch(this.url + _endpoint, { method: 'GET' })
      .then(this._handleError)
      .then(res => res.json())
      .catch(error => {
        alert('So sad. There was an error.');
        throw new Error(error);
      });
  }
};

The public API.get() method returns a Promise . The technical lead now requires switching the data‑fetching mechanism from fetch to Axios . The required changes are:

Replace window.fetch() with axios.get() while keeping the same return type.

Adjust error handling because Axios resolves with a data property instead of needing res.json() .

Keep API.url unchanged.

Method 1: Delete and Rewrite Code

class API {
  constructor() {
    this.url = 'http://whatever.api/v1/';
  }

  _handleError(_res) {
    return _res.statusText === 'OK' ? _res : Promise.reject(_res.statusText);
  }

  get(_endpoint) {
    return axios.get(this.url + _endpoint)
      .then(this._handleError)
      .then(res => res.data)
      .catch(error => {
        alert('So sad. There was an error.');
        throw new Error(error);
      });
  }
};

This approach works but can become messy when multiple fetching strategies are needed.

Method 2: Refactor with Adapter Pattern

Introduce a FetchAdapter that isolates all Fetch‑specific logic.

class FetchAdapter {
  _handleError(_res) {
    return _res.ok ? _res : Promise.reject(_res.statusText);
  }

  get(_endpoint) {
    return window.fetch(_endpoint, { method: 'GET' })
      .then(this._handleError)
      .then(res => res.json());
  }
};

Refactor the API module to depend on an adapter, defaulting to FetchAdapter :

class API {
  constructor(_adapter = new FetchAdapter()) {
    this.adapter = _adapter;
    this.url = 'http://whatever.api/v1/';
  }

  get(_endpoint) {
    return this.adapter.get(this.url + _endpoint)
      .catch(error => {
        alert('So sad. There was an error.');
        throw new Error(error);
      });
  }
};

Create an AxiosAdapter with Axios‑specific handling:

const AxiosAdapter = {
  _handleError(_res) {
    return _res.statusText === 'OK' ? _res : Promise.reject(_res.statusText);
  },
  get(_endpoint) {
    return axios.get(_endpoint)
      .then(this._handleError)
      .then(res => res.data);
  }
};

Switch the default adapter in API to AxiosAdapter when needed:

class API {
  constructor(_adapter = new AxiosAdapter()) {
    this.adapter = _adapter;
    this.url = 'http://whatever.api/v1/';
  }
  // ... same get method as above
};

Now you can instantiate a legacy API that still uses FetchAdapter while the rest of the codebase uses the Axios‑based implementation:

const api = new API(); // uses AxiosAdapter by default
const legacyAPI = new API(new FetchAdapter()); // uses FetchAdapter

When deciding how to evolve a project, compare the simplicity of deleting and rewriting code against the flexibility of refactoring with adapters. Over‑abstraction can increase complexity, so choose the approach that best fits your scenario.

Conclusion: Use adapters to keep your codebase maintainable and extensible, but avoid unnecessary abstraction layers.

JavaScriptAxiosfetchAPIAdapter PatternModule Refactoring
Hujiang Technology
Written by

Hujiang Technology

We focus on the real-world challenges developers face, delivering authentic, practical content and a direct platform for technical networking among developers.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.