From a lightweight HTTP client to a modular transport layer for modern TypeScript applications.
Today, I'm releasing tanstack-fetch 2.0.0.
This is more than another feature release. Since v1.7.1, tanstack-fetch has evolved beyond traditional HTTP requests. It now supports multiple communication transports, including WebSockets, WebTransport, and NDJSON streams, while keeping its familiar API and TypeScript-first approach.
Version 2.0.0 also brings an important architectural change: a significantly smaller HTTP core, with optional functionality moved into dedicated plugins.
Let's look at what changed and why.
What's new since v1.7.1?
The journey from 1.7.1 to 2.0.0 includes two releases:
- v1.8.0: Introduced new live communication transports.
- v2.0.0: Refactored the core architecture and improved type safety for live APIs.
Let's break them down.
1. WebSocket support
HTTP isn't always the right transport.
Applications such as chat systems, collaborative tools, dashboards, and real-time applications often need persistent, bidirectional communication.
In v1.8.0, I introduced api.ws().
It provides WebSocket communication through the same client architecture used by HTTP.
import { createFetch } from 'tanstack-fetch'
const api = createFetch({
baseUrl: 'https://api.example.com',
})
const socket = api.ws<Message, { roomId: string }, SendMessage>(
'/rooms/:roomId',
{
params: { roomId: 'general' },
},
)
socket.onMessage((message) => {
console.log(message)
})
socket.send({
text: 'Hello!',
})
Key capabilities include:
- Automatic reconnection with exponential backoff.
- Fresh authentication tokens on reconnection attempts.
- Ordered message queuing until the connection opens.
- JSON message parsing.
- AbortSignal support.
- Connection lifecycle callbacks.
- Integration with the shared interceptor architecture.
Authentication is also designed with platform differences in mind. Node.js 22+, Bun, and Deno support Authorization headers. Browsers and workers can explicitly use query or protocol-based authentication when needed.
Tokens are never placed in URLs by default.
2. WebTransport support
WebSocket isn't the only option for persistent communication.
Version 1.8.0 also introduced api.transport() for WebTransport.
WebTransport provides an HTTP/3-based transport with support for datagrams and other communication patterns.
const connection = api.transport('/live', {
// WebTransport-specific options
})
The API supports WebTransport-specific configuration, including serverCertificateHashes, and a createWebTransport option for environments that need a custom implementation or polyfill.
This makes it possible to work with different transport technologies without building an entirely separate client architecture.
3. NDJSON streaming with api.stream()
Another major addition is api.stream().
It supports NDJSON (Newline Delimited JSON), allowing a server to send JSON objects incrementally instead of waiting for an entire response.
const stream = api.stream<Message>('/events', {
method: 'POST',
body: {
prompt: 'Explain how streaming works',
},
})
for await (const message of stream) {
console.log(message)
}
Unlike a conventional JSON response, NDJSON can be consumed progressively.
The implementation also supports:
- Any HTTP method and request body.
- The shared interceptor pipeline.
- Status handlers and authentication refresh.
- Retry behavior.
- AbortSignal cancellation.
- Integration with TanStack Query's
streamedQuery.
It also handles Node.js streaming requirements by setting duplex: 'half' for ReadableStream request bodies.
4. A unified live transport architecture
With WebSocket, WebTransport, and NDJSON support, managing live communication separately from HTTP would create unnecessary complexity.
That's why these transports share the same underlying client architecture.
Version 1.8.0 introduced a dedicated entry point:
import { createFetch } from 'tanstack-fetch/live'
It exposes:
api.sse()api.ws()api.transport()api.stream()
The existing tanstack-fetch/sse entry point remains unchanged and lightweight.
The shared architecture also introduces live-specific interceptor hooks:
onLiveOpenonLiveMessageonLiveClose
These callbacks make it possible to observe incoming and outgoing messages and connection lifecycle events.
The transport type is available through:
context.meta.transport
Possible values include websocket, webtransport, and ndjson.
5. DevTools improvements
Debugging live communication can be difficult, especially when multiple connections and messages are involved.
Version 1.8.0 extends DevTools with dedicated tabs:
- WS: WebSocket and WebTransport connections.
- Stream: NDJSON streams.
Each connection includes a message log, making it easier to inspect communication during development.
The CLI also gained improvements:
-
generatecan generateapi.streamfor NDJSON operations. -
doctordetects rawnew WebSocketandnew EventSourceusage. - It checks whether
api.streamcorrectly receives an AbortSignal insidequeryFn.
6. A smaller HTTP core in v2.0.0
Now comes one of the most important changes.
The core package has been reduced from 5.14 KB to 3.57 KB gzip.
That's approximately a 30% reduction.
The goal isn't to remove functionality from the ecosystem. It's to keep the HTTP core focused and move optional functionality into dedicated modules.
The core createFetch configuration now focuses on:
baseUrlheaderssourceincomingtimeoutMsinterceptorsfetchcredentialsmaxRetries
Authentication and status handling are now provided through dedicated interceptors.
For example:
import { createFetch } from 'tanstack-fetch'
import {
createAuthInterceptor,
createStatusInterceptor,
} from 'tanstack-fetch/plugins'
const api = createFetch({
baseUrl: 'https://api.example.com',
interceptors: [
createAuthInterceptor({
getToken: () => getAccessToken(),
}),
createStatusInterceptor({
401: async () => {
// Handle unauthorized responses
},
'5xx': async () => {
// Handle server errors
},
}),
],
})
This is a more composable architecture.
Instead of making every application carry authentication and status-handling functionality in its core configuration, developers can explicitly add the behavior they need.
Other interceptors, such as tracing and token refresh, can also be composed through the plugin architecture.
What moved out of core?
The following functionality is no longer part of the core createFetch API:
getTokenauthplugins: [...]onStatusonUnauthorizedonForbidden- Other status shortcut callbacks
upload()- XHR-based upload and download progress events
The important distinction is that this is a change to the core HTTP client, not a removal of these capabilities from the entire library.
The tanstack-fetch/sse, /live, /trpc, and framework provider entry points retain their existing options, upload functionality, and progress events.
For applications relying on the previous upload API, switching to tanstack-fetch/sse provides a drop-in upgrade path.
7. Type-safe parameters for live APIs
Version 2.0.0 also brings an important TypeScript improvement.
Previously, live transport APIs didn't offer the same path-parameter inference available in HTTP methods.
Now, live APIs support typed route parameters.
const socket = api.ws<Message, { roomId: number }>(
'/rooms/:roomId',
{
params: {
roomId: 42,
},
},
)
The same approach is available across:
api.sse()api.ws()api.transport()api.stream()useSse()useWs()
Framework bindings now support this improvement across React, Vue, Solid, Svelte, and Angular.
The generic parameter order has also been aligned with the HTTP API:
// WebSocket
<Message, Params, Send>
// SSE / NDJSON
<Message, Params>
If you previously used the old WebSocket generic order, you'll need to update your type arguments. Use NoParams when you don't need route parameters.
New exported types include:
LiveParamsFetchMethodPathFetchParamsConstraint
The OpenAPI generate command also emits parameter types for SSE and NDJSON operations.
8. Important bug fixes
Version 2.0.0 also includes improvements to the NDJSON streaming lifecycle.
Node.js ReadableStream request bodies
api.stream() now sends duplex: 'half' when a ReadableStream is used as a request body, as required by Node.js/undici.
Network errors
Network failures now correctly trigger onRequestError, allowing retry or short-circuit behavior.
Abort during retry delay
Aborting a stream during a retry delay now rejects immediately instead of waiting for the delay to finish.
9. Migration from 1.7.1 to 2.0.0
Because 2.0.0 introduces breaking changes, upgrading requires reviewing your client configuration.
Step 1: Update the package
npm install tanstack-fetch@2.0.0
Step 2: Move authentication and status handlers to interceptors
If your previous configuration looked like this:
const api = createFetch({
baseUrl: '/api',
getToken,
onUnauthorized: handleUnauthorized,
})
Move those responsibilities into the plugin architecture:
import {
createAuthInterceptor,
createStatusInterceptor,
} from 'tanstack-fetch/plugins'
const api = createFetch({
baseUrl: '/api',
interceptors: [
createAuthInterceptor({ getToken }),
createStatusInterceptor({
401: handleUnauthorized,
}),
],
})
Step 3: Review upload usage
The core client no longer exposes upload() or XHR progress callbacks.
If you depend on these features, use the tanstack-fetch/sse entry point, which retains the previous upload functionality.
Step 4: Update WebSocket generic arguments
If you used the previous generic order:
api.ws<Message, Send>('/rooms/:id')
Update it to:
api.ws<Message, { id: number }, Send>(
'/rooms/:id',
{
params: { id: 1 },
},
)
Review other live APIs for the new typed params argument as well.
What's next?
The direction behind tanstack-fetch is becoming clearer.
I don't want to build another library that simply wraps fetch() with a different syntax.
The goal is to provide a predictable, type-safe transport layer that works naturally with TanStack Query and modern TypeScript applications.
HTTP, SSE, WebSockets, WebTransport, and NDJSON are different communication mechanisms, but they shouldn't require completely different approaches to authentication, error handling, observability, and cancellation.
With v2.0.0, the foundation is smaller, the architecture is more modular, and live communication is more consistent.
tanstack-fetch 2.0.0 is available now.
npm install tanstack-fetch
Project links
- Documentation: https://mohamadgarmabi.github.io/tanstack-fetch/
- GitHub: https://github.com/mohamadgarmabi/tanstack-fetch
- npm: https://www.npmjs.com/package/tanstack-fetch
- Release notes: https://mohamadgarmabi.github.io/tanstack-fetch/blog/tanstack-fetch-2-0-0
I'm the creator and maintainer of tanstack-fetch. This is an independent open-source project, not an official TanStack package.
I'd love to hear your feedback, especially from developers building applications with real-time data and TanStack Query.
Top comments (0)