Skip to content
v2.0.0 3.57KB core · Typed live params

tanstack-fetch

Typed fetch,
made simple.

One request. Data back on success, typed FetchError on failure, AbortSignal honored — shaped for TanStack Query. SSR, SSE, WebSocket, upload, refresh-token, and tRPC stay optional.

api.tslive
  1. GETapi.get<User[]>('/users', { signal })
    200[{ id: '1', name: 'Ada' }, …]User[]42ms
  2. GETapi.get<User>('/me')
    200{ id: 'u_1' } · retried once ↻ refreshed User96ms
  3. GETapi.get('/admin')
    403{ status: 403, code: 'HTTP_ERROR', body }FetchError18ms
  4. POSTapi.upload('/files', { file, onUploadProgress }) // tanstack-fetch/sse
    201{ id: 'f_91', size: '2.4MB' }Upload1.2s
  5. WSapi.ws<Msg, { id: string }>('/rooms/:id', { params, onMessage })
    101{ text: 'hi' } · reconnected, fresh token ↻ refreshed WsSocketlive
data on successFetchError on failureAbortSignal honored

What's new

  1. v2.0.0Latest3.57KB core · Typed live paramsAuth, status and plugins as interceptors on a slim core; sse, ws, transport and stream type params like api.get. Read the post
  2. v1.8.0WebSocket · WebTransport · NDJSONapi.ws, api.transport and api.stream on tanstack-fetch/live, plus useWs in every framework. Read the post
  3. v1.7.1Timeouts · URLs · ErrorsTimeoutError on timeouts, ports no longer read as params, binary bodies sent as bytes. Read the post
  4. v1.7.0doctor · Solid · Angular · Sveltetanstack-fetch doctor health check, real Solid / Angular / Svelte adapters, migrate scaffolds. Read the post
  5. v1.6.3Migrate CLIRewrite axios, ky, ofetch, $fetch and fetch call sites for TanStack Query. Read the post
  6. v1.6.1Typed path paramsapi.get<User, { id: number }>(…) params maps and optional body typing on post / put / patch. Read the post
  7. v1.5.0Vue & NuxtcreateFetchPlugin, useFetch and useSse for Vue 3 and Nuxt, same client as React. Read the post
  8. v1.4Refresh token · DevToolsToken refresh without hand-written interceptors, a DevTools dock, and useSse without a provider. Read the post

What you get

One client, every call site.

Replace axios, ky, ofetch, or raw fetch with one createFetch client — same queryFn shape on React, Vue, Nuxt, Next.js, Solid, Angular, Svelte, and SvelteKit. Migrate guide + CLI

TS

Return data, throw on failure, and pass the signal. That is the whole queryFn — React, Vue, Solid, Svelte, or Angular Query.

import { createFetch } from 'tanstack-fetch'
import { useQuery } from '@tanstack/react-query'

const api = createFetch({ baseUrl: 'https://api.example.com' })

useQuery({
  queryKey: ['users'],
  queryFn: ({ signal }) =>
    api.get<User[]>('/users', { signal }),
})
result output
// data is User[]
[
  { id: '1', name: 'Ada' },
  { id: '2', name: 'Grace' },
]

// isPending → false
// isError → false

Why not axios?

Same job. Query-shaped answer.

axios, ky, and ofetch are solid HTTP clients. tanstack-fetch is shaped for the call site TanStack Query already expects.

  • Built for queryFn

    axios wraps responses. tanstack-fetch returns data, throws FetchError, and takes signal.

  • SSR cookies without glue

    ssr-forward ships cookie and auth headers from the incoming request. No hand-rolled adapter.

  • Realtime that can authenticate

    SSE over fetch sends Authorization. api.ws, api.transport, and api.stream reuse the same token and refresh-token. No second socket library.

30-second start

Create a client. Pass the signal.

Install the package, create a client, pass signal. That is the whole setup.

app.ts
import { createFetch } from 'tanstack-fetch'
import { useQuery } from '@tanstack/react-query'

const api = createFetch({ baseUrl: '/api' })

useQuery({
  queryKey: ['users'],
  queryFn: ({ signal }) => api.get('/users', { signal }),
})

What the queryFn gets

  1. 1
    Data on successParsed JSON, typed by your generic.
  2. 2
    FetchError on failurestatus, code, and body — Query's error state.
  3. 3
    AbortSignal honoredUnmounts and refetches cancel in flight.
Get started

Features

Everything a queryFn client needs.

  • Query-shaped client

    Success returns data. Failure throws. Cancellation follows AbortSignal.

  • Typed FetchError

    status, code, and body are on the error. isFetchError narrows it.

  • Status handlers

    401, 403, 404, 429, other 4xx, and 5xx each have a hook.

  • Refresh on 401

    createRefreshTokenInterceptor: before (time) and after (first 401), single-flight.

  • Request DevTools

    setupDevtools(api) shows HTTP, SSE, SSR, and tRPC with attempts, timing, and caller file.

  • 3.57KB HTTP core

    The HTTP entry is the core. SSE, WebSocket, React, Vue, plugins, and tRPC are separate imports.

  • Plugins

    Named interceptors for retry, trace, and mocks. Eject one on a single call.

  • SSR forwarding

    Next.js and other server renders can forward cookies and auth headers.

  • SSE with auth

    Streams use fetch, so Authorization is a normal header. EventSource cannot send it.

  • WebSocket with auth

    api.ws: Authorization header on Node/Bun/Deno, explicit query/protocol in browsers, reconnect with a fresh token.

  • WebTransport & NDJSON

    api.transport for HTTP/3 datagrams, api.stream for line-by-line JSON that plugs into streamedQuery.

  • Upload progress

    Multipart upload + download progress (onUploadProgress / onDownloadProgress) on tanstack-fetch/sse.

  • tRPC link

    Router and Start can share the same token, plugins, and base client.

  • OpenAPI codegen

    Generate a typed client from an OpenAPI document with the bundled CLI.

  • React helpers

    FetchProvider, useFetch, useSse, and useWs when a component tree should share one client.

  • Vue & Nuxt helpers

    createFetchPlugin, useFetch, useSse, and useWs for Vue 3 and Nuxt apps.

Entry points · v2.0.0

Import only the piece you call.

None of these are required on day one. Add an entry when the feature shows up — Solid, Angular, and Svelte stay on the HTTP core; React / Vue adapters and CLIs are optional.

Playground

Click a status. Open a socket. It runs in the browser.

createFetch against mock servers: HTTP status handlers, WebSocket, WebTransport, and NDJSON streams. It is the same client, not a screenshot.

Live demo · real createFetch

Trigger HTTP statuses

Mock fetch returns the code you click. Handlers fire before FetchError is thrown — including 429 with Retry-After.

Tap a status — real createFetch + mock fetch

Event log

Handlers and throws show up here.

const api = createFetch({
  interceptors: [
    createStatusInterceptor({
      401: () => logout(),
      429: ({ context }) => {
        const waitMs = parseRetryAfter(context.response?.headers)
        // show toast / backoff…
      },
      '4xx': ({ status }) => console.warn('4xx', status),
    }),
  ],
})

Open in StackBlitz · v2.0.0

Run the examples in the browser.

StackBlitz for React demos (Query, Next.js, SSE, tRPC, upload). Docs cover the same api.sse / api.ws / api.stream / createTRPCFetchClient patterns on Vue, Nuxt, Solid, Angular, Svelte, and SvelteKit.

Comparison

Compared with the usual clients.

axios, ky, and ofetch are all capable HTTP clients. tanstack-fetch is shaped for the TanStack Query call site, and for the SSR, SSE, and tRPC cases that sit next to it.

Featuretanstack-fetchaxioskyofetch
queryFn returns data, throws, and takes signal First-classPartialPartialPartial
Typed error with status and body First-classPartialPartialPartial
Handlers for 401 through 429 First-classNot built inNot built inNot built in
Refresh before expiry + after first 401 First-classPartialNot built inNot built in
Next.js cookie forwarding First-classNot built inNot built inNot built in
SSE that can send Authorization First-classNot built inNot built inNot built in
WebSocket with auth, reconnect, and send queue First-classNot built inNot built inNot built in
NDJSON streaming (POST body + headers) First-classPartialPartialPartial
Upload progress First-classFirst-classNot built inNot built in
Request DevTools for HTTP, SSE, SSR, tRPC First-classNot built inNot built inNot built in
tRPC link on the same client First-classNot built inNot built inNot built in
Built in11/111/110/110/11
  • First-class
  • Possible, with extra adaptation
  • Not built in
Full comparison

Start with one request.

Add a token when you need auth, a plugin when you need retry, another entry when you need SSE, WebSocket, WebTransport, or NDJSON streams. The queryFn does not change. The first call is a few lines.

MIT Licensed · Created by Mohammad Garmabi · Not an official TanStack package