Blog
3 min read

Fetch vs Axios: Which Should You Use for HTTP Requests?

fetch is built into browsers and Node.js; Axios is a popular library with extra conveniences. How they differ on JSON handling, errors, timeouts, interceptors and upload progress, the same request in both, and why fetch is usually enough now.

To call an API from JavaScript, you'll use either fetch — built into every modern browser and into Node.js 18+ — or Axios, a library you install. Both work. The differences are in the conveniences.

The same request in both

fetch:

const res = await fetch('/api/orders', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ productId: 42 }),
})
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const order = await res.json()

Axios:

import axios from 'axios'

const { data: order } = await axios.post('/api/orders', { productId: 42 })

Axios is shorter because it does three things for you: converts the body to JSON, parses the JSON response, and throws on error status codes.

The differences that matter

1. Errors

This is the big one. fetch only rejects on network failure. A 404 or 500 is a "successful" fetch — you must check res.ok yourself. Forgetting this is a classic bug. (TypeError: Failed to fetch)

Axios rejects on any status outside 2xx, so a try/catch catches HTTP errors too.

2. JSON

fetch: JSON.stringify the body, set the Content-Type header, call res.json(). Axios: automatic both ways.

3. Timeouts

fetch has no timeout option, but AbortSignal.timeout() does it in one line:

await fetch(url, { signal: AbortSignal.timeout(5000) })

Axios: axios.get(url, { timeout: 5000 }).

4. Interceptors

Axios lets you run code on every request or response — add an auth header, refresh an expired token, log errors:

api.interceptors.request.use(config => {
  config.headers.Authorization = `Bearer ${getToken()}`
  return config
})

With fetch, you get the same effect by writing a small wrapper function.

5. Upload progress

Axios reports upload progress (onUploadProgress), handy for progress bars on file uploads. fetch can't report upload progress as simply. (Presigned URL uploads)

6. Size and dependencies

fetch is built in — zero kilobytes, zero dependencies. Axios adds a dependency (and, like any package, something to keep updated). (npm supply chain security)

Side by side

fetch Axios
Install needed No Yes
Throws on 404/500 No — check res.ok Yes
JSON handling Manual Automatic
Timeout AbortSignal.timeout() timeout option
Interceptors Write a wrapper Built in
Upload progress Awkward Built in
Works in Next.js server caching Yes (Next extends fetch) No

That last row matters if you use Next.js: its data caching and revalidation are built around fetch. (Next.js caching and server components)

A small fetch wrapper gets you most of Axios

export async function api(path, { body, ...options } = {}) {
  const res = await fetch(`/api${path}`, {
    ...options,
    headers: { 'Content-Type': 'application/json', ...options.headers },
    body: body ? JSON.stringify(body) : undefined,
    signal: options.signal ?? AbortSignal.timeout(10000),
  })
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`)
  return res.status === 204 ? null : res.json()
}

Which should you use?

  • New project, especially Next.js: fetch (plus a small wrapper).
  • You need upload progress or lots of interceptor logic: Axios is convenient.
  • Existing project using Axios: no reason to rip it out.
  • For data fetching in React components, consider a library like TanStack Query on top of either. (TanStack Query vs useEffect)

EasySpawn runs your frontend and API side by side on one server, so requests stay same-origin and there are no CORS headaches to fetch or Axios around. See how it works or join the waitlist.

Related: JavaScript Promises Explained · Async/Await in JavaScript · TypeError: Failed to Fetch · CORS Errors Explained

Keep reading