DEV Community

Why do people use Axios instead of Fetch

Nikolas ⚑️ on January 29, 2024

Axios vs. Fetch In the dynamic realm of JavaScript and front-end development, selecting the appropriate tool for HTTP requests is critic...
Collapse
 
manchicken profile image
Mike Stemle • Edited

There are a few extra aspects of the Fetch API that I think folks forget about:

  • You have to make sure to regularly update axios to keep up with security problems, but not with the Fetch API.
  • axios takes up space in your node_modules and bundle, but the Fetch API does not.
  • The Fetch API works with the built-in URL API, but axios does not.

While I know that this isn't the point of your article, I think it's important to note that String Manipulation of URLs is an Anti-Pattern.

Collapse
 
jwhenry3 profile image
Justin Henry

In many cases, the biggest argument against using fetch directly is running the same code in node and in the browser. Fetch is not accessible in node, so you would need to use a library like node-fetch in order to access similar functionality on the backend. Rather than taking this route, many REST libraries can run on both sides because they polyfill on the inside.

Collapse
 
florianrappl profile image
Florian Rappl

This is outdated. You can use fetch in recent versions.

Anyway, the argument of having a stable layer in between is still compelling.

Another argument to make is that axios gives you a better thought out API and more options - eg having a progress callback when uploading or downloading large files.

Collapse
 
jwhenry3 profile image
Justin Henry

I agree its outdated, however, many companies run with older versions of technologies and shifting the infrastructure requires jumping through hoops. In an ideal scenario, those in charge of infrastructure should keep up with the versions, but unfortunately its not always the case.

Thread Thread
 
dscheglov profile image
Dmytro Shchehlov

We got rid of node-fetch from a large nextjs-project (both server and client side) in a single commit and two files changed:

  • package.json
  • services.ts (the composition root for our DI)

The problem is not in the fetch, the problem is that view directly depends on the implementation isntead of depending on the abstraction.

Collapse
 
dscheglov profile image
Dmytro Shchehlov • Edited

So, detailed research. But I guess it contains a problem, that could be expressed with the simple question: why React components/hooks have to warry about fetch or axios?

React components MUST rely on the abstraction, that MUST be provided on the highest level as it is possible (ideally on the application level, but considering SPA architecture it could be a "page"-level or something like that).

Let's guess we need to display the Books fetched via some rest-like API.
It doesn't mean that we need to write fetch requests inside of the react-components or even hooks.

We need to create an interface (or type):

interface IBooksProvider {
  (libraryId: string): Promise<Book[]>
}
Enter fullscreen mode Exit fullscreen mode

Then we need to declare correspondent dependency inside of the react-component or hook. For simplicity let's select the context api

const booksProviderContext = React.createContext<IBooksProvider>(async () => {
  throw new ReferenceError("BookProvider is not injected");
});
Enter fullscreen mode Exit fullscreen mode

then we can use it:

type LibraryBooksProps = {
 libraryId: string;
}

type LibraryBooksState = {
  books: Book[];
  isLoading: boolean;
  error?: unknown;
}

const LibraryBooks = ({ libraryId }: LibraryBooksProps) => {
  const getBooks = React.useContext(booksProviderContext);
  const [state, setState] = React.useState<LibraryBooksState>({ 
    books: [],
    isLoading: true,
  });

  useEffect(async () => {
    setState({ books: [], isLoading: true });
    try {
       const books = await getBooks(libraryId);
       setState({ books, isLoading: false }); 
    } catch(error) {
       setState({ books: [], isLoading: false, error });
    }
  }, [getBooks, libraryId]);

  useEffect(() => {
    if (state.error) throw state.error; // we need to re-throw an async error
    // or you can just use `react-error-boundary` library instead
  }, [state.error]);

  return (state.isLoading 
     ? <Spinner> 
     : <BooksList>
        {state.books.map(
           book => <Book key={book.id} {...book} />
        )}
      </BooksList>
   );
}
Enter fullscreen mode Exit fullscreen mode

Having this code we can write tests for this components WITHOUT mocking the fetch.

Please note, that component LibraryBooks doesn't know anything about the source of the data, it is not aware of authorization, content types, http-respones, base urls, protocols etc.

Sure, we need to create function that implements the IBooksProvider interface.
But we should to do the same.
This function MUST NOT depend directly on the fetch or axios. Again, it MUST depend on the abstraction:

interface IHttpClient {
  get(path: string, options?: HttpOptions): Promise<HttpResponse>;
  // ... -- snip --
}
Enter fullscreen mode Exit fullscreen mode

Where the types HttpOptions and HttpResponse could be as narrow as it allows your project (the backend you are using, or are going to use).

Let's guess, the following:

type HttpOptions = {
  headers?: Record<string, string>;
  signal?: AbortSignal;
};

type HttpResponse = {
  status: number;
  statusText: string;
  body: unknown;
};
Enter fullscreen mode Exit fullscreen mode

Then we need to create a function getLibraryBooks:

const getLibraryBooks =
  (http: Pick<IHttpClient, "get">) =>
  (libraryId: string) =>
     http.get(`/library/${libraryId}/books`).then(
       ({ status, statusText, body }) => {
         if (status === 200) {
          // the body could be checked against some schema,
          // written with something like zod or io-ts
           return body as Book[];
         }
         if (status === 404) {
           // better to return null here, but for our
           // example let it be an empty array
           return [];
         }
         throw new Error(
           `Failed to fetch books: ${status} - ${statusText}\n${String(body)}`
         );
      }
    );
Enter fullscreen mode Exit fullscreen mode

Again, we can write tests for this function. Note, it is not aware of the base url and authorization. Also it assumes that httpClient deserializes body according to the response headers.
Please note, that for the function getLibraryBooks any response status except of 200 and 404 is unexpected, so it can just throw an error (not httpClient, but this function)

So, now we need to write an implementation for our IHttpClient.
And only now we should choose the fetch or axios. More then, this implementation MUST NOT depend on the fetch or axios-instance directly, it must depend on the abstraction, and allow to inject the correspondent instances.

The practice shows that such implementation has the same complexity for fetch and axios. We had a nextjs-project (used fetch) and pure back-end nodejs project (used axios) and we have a service that must work on both projects. So we had to implement the IHttpClient for both: fetch and axios to allow to use this Service in both projects. Later when we rewrite other services of the back-end project to use IHttpClient instead of axios, we got rid of the last one, and after node20 LTS released we removed the node-fetch from the both projects.

No react-component suffered )

Summarizing:

  1. Create a onion architecture even for Front-end, where the View Layer is not aware of the Data Layer details
  2. Use dependency injection patterns to compose the application according to the your project needs
  3. Follow SOLID even if you are a fan of the functional programming
Collapse
 
ravavyr profile image
Ravavyr

The title of this article should be modified to say "... in React".

Not all of us use Axios, because not all of us need it.
Fetch works perfectly fine in vanilla JS applications.

Collapse
 
_ndeyefatoudiop profile image
Ndeye Fatou Diop

Thanks for the great post!!! A lot of details! There are also axios interceptors that are very cool to use 😊

Collapse
 
psypher1 profile image
James 'Dante' Midzi

I have never understood why people insists on installing something for things you can already natively do.

Collapse
 
gene profile image
Gene

This article is not for beginners.