DEV Community

Mohammad Garmabi
Mohammad Garmabi

Posted on

Stop adapting axios to TanStack Query: a fetch client built for queryFn

TanStack Query doesn't care how you fetch. It only asks one thing of your queryFn: return data, throw on failure, and respect the AbortSignal.

Most HTTP clients don't do that out of the box, so every project grows the same glue.

The usual glue

queryFn: async ({ signal }) => {
  try {
    const res = await axios.get('/users', { signal })
    return res.data
  } catch (e) {
    if (axios.isAxiosError(e) && e.response?.status === 401) {
      await refreshToken() // what if 5 queries hit 401 at once?
      // retry...?
    }
    throw normalizeError(e)
  }
}
Enter fullscreen mode Exit fullscreen mode

Multiply that by every endpoint, add SSR cookie forwarding and an SSE stream that needs an Authorization header, and you have a small framework nobody meant to write.

The same thing with tanstack-fetch

import { createFetch } from 'tanstack-fetch'
import { createAuthInterceptor, createStatusInterceptor } from 'tanstack-fetch/plugins'

export const api = createFetch({
  baseUrl: 'https://api.example.com',
  interceptors: [
    createAuthInterceptor({ getToken: () => localStorage.getItem('access_token') }),
    createStatusInterceptor({ 401: () => logout() }),
  ],
})

useQuery({
  queryKey: ['users'],
  queryFn: ({ signal }) => api.get<User[]>('/users', { signal }),
})
Enter fullscreen mode Exit fullscreen mode
  • Returns the parsed body directly
  • Throws FetchError, and isFetchError(e) gives you status and body, fully typed
  • Passes signal through, so Query cancellation just works

Typed errors in the UI

const { error } = useQuery(...)
if (isFetchError(error) && error.status === 404) return <NotFound />
Enter fullscreen mode Exit fullscreen mode

Only pay for what you use

Import What gzip
tanstack-fetch slim HTTP core 3.57KB
tanstack-fetch/plugins interceptors 1.13KB
tanstack-fetch/sse + SSE, upload, progress 6.33KB
tanstack-fetch/react provider + hooks 0.99KB

There are also entry points for Vue/Nuxt, Solid, Svelte, Angular, tRPC and a DevTools dock.

SSE with auth

The browser's EventSource can't send headers. tanstack-fetch/sse can:

import { createFetch } from 'tanstack-fetch/sse'

const api = createFetch({
  baseUrl: 'https://api.example.com',
  getToken: () => localStorage.getItem('access_token'),
})

const stream = api.sse<OrderEvent>('/orders/stream', {
  onMessage: (data) => console.log(data),
})

stream.close()
Enter fullscreen mode Exit fullscreen mode

Migrating

npx tanstack-fetch doctor
Enter fullscreen mode Exit fullscreen mode

It scans your project and scaffolds a migration from axios, ky or ofetch.

Links

Not an official TanStack package. It's built for the same mental model. I'd love feedback in the comments, especially on where the core/plugin line should be.

Top comments (0)