Getting started

Install
Peers are optional. Pick the stack you use:
# Vue / Nuxt
npm install tanstack-fetch vue
# optional Query
npm install @tanstack/vue-queryFor SSE, WebSocket / WebTransport / NDJSON (tanstack-fetch/live), React, Vue/Nuxt, or tRPC, import the matching entry — same package.
React quickstart
Framework
React · @tanstack/react-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 }),
})That’s it: returns data, throws FetchError on HTTP errors, honors Query’s signal.
Vue quickstart
Framework
Vue 3 · optional @tanstack/vue-query · Nuxt-ready
import { createFetch } from 'tanstack-fetch'
import { useQuery } from '@tanstack/vue-query'
const api = createFetch({ baseUrl: 'https://api.example.com' })
useQuery({
queryKey: ['users'],
queryFn: ({ signal }) => api.get<User[]>('/users', { signal }),
})Or inject a shared client with tanstack-fetch/vue.
Shared client (recommended)
Framework-agnostic — works in React, Vue, Nuxt, and Next.js.
// src/lib/api.ts
import { createFetch } from 'tanstack-fetch'
import {
createAuthInterceptor,
createRetryIdempotentInterceptor,
createStatusInterceptor,
createTraceInterceptor,
} from 'tanstack-fetch/plugins'
export const api = createFetch({
baseUrl: import.meta.env.VITE_API_URL ?? 'https://api.example.com',
interceptors: [
createAuthInterceptor({ getToken: () => localStorage.getItem('access_token') }),
createStatusInterceptor({
401: () => {
localStorage.removeItem('access_token')
window.location.href = '/login'
},
}),
createTraceInterceptor(),
createRetryIdempotentInterceptor(),
],
})The core tanstack-fetch entry is slim (3.57KB) — auth, status handlers and plugins are interceptors. Prefer getToken / onUnauthorized / plugins: [...] shortcuts or need upload()? Import createFetch from tanstack-fetch/sse (or /live) — same options, plus those. See Core entry points.
Quick taste — typed params & body
import { createFetch, isFetchError } from 'tanstack-fetch'
import type { NoParams } from 'tanstack-fetch'
import { createAuthInterceptor } from 'tanstack-fetch/plugins'
const api = createFetch({
baseUrl: import.meta.env.VITE_API_URL,
interceptors: [createAuthInterceptor({ getToken: () => localStorage.getItem('access_token') })],
})
const user = await api.get<User, { id: string | number }>('/users/:id', { params: { id: '1' } })
await api.post<User, NoParams, { name: string }>('/users', { body: { name: 'Ada' } })
try {
await api.get('/missing')
} catch (error) {
if (isFetchError(error)) console.log(error.status, error.message)
}Types — one sentence
- No generics →
paramsinferred from the URL (:id/{id}) get<T, Params>→ response type + params map (path must contain those keys)post<T, Params, Body>→ optional third generic for the request body (NoParamsskips params)
Next steps
| Goal | Page |
|---|---|
| Typed URL params / body | (see Quick taste above) |
| Cursor / agent skill | Agent skill |
| Token + 401 / 403 / 404 | Configuration |
| Refresh token | Refresh token |
| React / Vue Query | TanStack Query |
| React helpers | React |
| Vue / Nuxt helpers | Vue & Nuxt |
| Next.js / Nuxt cookies | SSR |
| Streams | SSE |
| WebSocket / NDJSON | WebSocket, WebTransport & streams |
| SSE auth | EventSource auth headers |
| tRPC | tRPC |