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)
}
}
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 }),
})
- Returns the parsed body directly
- Throws
FetchError, andisFetchError(e)gives youstatusandbody, fully typed - Passes
signalthrough, so Query cancellation just works
Typed errors in the UI
const { error } = useQuery(...)
if (isFetchError(error) && error.status === 404) return <NotFound />
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()
Migrating
npx tanstack-fetch doctor
It scans your project and scaffolds a migration from axios, ky or ofetch.
Links
- Docs & playground: https://mohamadgarmabi.github.io/tanstack-fetch/
- GitHub: https://github.com/mohamadgarmabi/tanstack-fetch
- npm: https://www.npmjs.com/package/tanstack-fetch
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)