# HTTP Batch Stream Link

> A terminating link that batches tRPC calls and streams each response as soon as it is ready

From https://trpc.io/docs/client/links/httpBatchStreamLink

`httpBatchStreamLink` is a [**terminating link**](https://trpc.io/docs/client/links#the-terminating-link) that batches an array of individual tRPC operations into a single HTTP request that's sent to a single tRPC procedure (equivalent to [`httpBatchLink`](https://trpc.io/docs/client/links/httpBatchLink)), but doesn't wait for all the responses of the batch to be ready and streams the responses as soon as any data is available.

## Options

Options are identical to [`httpBatchLink options`](https://trpc.io/docs/client/links/httpBatchLink#options), with the following addition:

| Option         | Type                          | Default         | Description                                                                                                                                                                                                                                                                                 |
| -------------- | ----------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `streamHeader` | `'trpc-accept'` \| `'accept'` | `'trpc-accept'` | Which header to use to signal the server that the client wants a streaming response. `'accept'` uses the standard `Accept` header instead of the custom `trpc-accept` header, which can avoid CORS preflight for cross-origin streaming queries since `Accept` is a CORS-safelisted header. |

## Usage

> All usage and options are identical to [`httpBatchLink`](https://trpc.io/docs/client/links/httpBatchLink).

> [!NOTE]
> If you require the ability to change/set response headers (which includes cookies) from within your procedures, make sure to use `httpBatchLink` instead! This is due to the fact that `httpBatchStreamLink` does not support setting headers once the stream has begun. [Read more](https://trpc.io/docs/client/links/httpBatchLink).


You can import and add the `httpBatchStreamLink` to the `links` array as such:

```ts no-check title="client/index.ts"
import { createTRPCClient, httpBatchStreamLink } from '@trpc/client';
import type { AppRouter } from './server';

const client = createTRPCClient<AppRouter>({
  links: [
    httpBatchStreamLink({
      url: 'http://localhost:3000',
    }),
  ],
});
```

After that, you can make use of batching by setting all your procedures in a `Promise.all`. The code below will produce exactly **one** HTTP request and on the server exactly **one** database query:

```ts no-check
const somePosts = await Promise.all([
  trpc.post.byId.query(1),
  trpc.post.byId.query(2),
  trpc.post.byId.query(3),
]);
```

## Streaming mode

When batching requests together, the behavior of a regular `httpBatchLink` is to wait for all requests to finish before sending the response. If you want to send responses as soon as they are ready, you can use `httpBatchStreamLink` instead. This is useful for long-running requests.

```ts no-check title="client/index.ts"
import { createTRPCClient, httpBatchStreamLink } from '@trpc/client';
import type { AppRouter } from './server';

const client = createTRPCClient<AppRouter>({
  links: [
    httpBatchStreamLink({
      url: 'http://localhost:3000',
    }),
  ],
});
```

Compared to a regular `httpBatchLink`, a `httpBatchStreamLink` will:

- Cause the requests to be sent with a `trpc-accept: application/jsonl` header (or `Accept: application/jsonl` when using `streamHeader: 'accept'`)
- Cause the response to be sent with `transfer-encoding: chunked` and `content-type: application/json` by default. You can set the streamed response `content-type` to `application/jsonl`, `application/x-ndjson`, or another value on the server via `initTRPC.create({ jsonl: { contentType: ... } })`.
- Remove the `data` key from the argument object passed to `responseMeta` (because with a streamed response, the headers are sent before the data is available)

## Async generators and deferred promises

You can try this out on the homepage of tRPC.io: [https://trpc.io/?try=minimal#try-it-out](https://trpc.io/?try=minimal#try-it-out)

```ts no-check
import { createTRPCClient, httpBatchStreamLink } from '@trpc/client';
import type { AppRouter } from './server';

const trpc = createTRPCClient<AppRouter>({
  links: [
    httpBatchStreamLink({
      url: 'http://localhost:3000',
    }),
  ],
});
const iterable = await trpc.examples.iterable.query();

for await (const value of iterable) {
  console.log('Iterable:', value);
}
```

## Compatibility (client-side)

### Browsers

Browser support should be identical to [`fetch`](https://caniuse.com/fetch) support.

### Node.js / Deno

For runtimes other than the browser ones, the `fetch` implementation should support streaming, meaning that the response obtained by `await fetch(...)` should have a `body` property of type `ReadableStream<Uint8Array> | NodeJS.ReadableStream`, meaning that:

- either `response.body.getReader` is a function that returns a `ReadableStreamDefaultReader<Uint8Array>` object
- or `response.body` is a `Uint8Array` `Buffer`

This includes support for `undici`, `node-fetch`, native Node.js fetch implementation, and WebAPI fetch implementation (browsers).

### React Native

Receiving the stream relies on the `TextDecoder` and `TextDecoderStream` APIs, which are not available in React Native. It's important to note that if your `TextDecoderStream` polyfill does not automatically polyfill `ReadableStream` and `WritableStream` those will also need to be polyfilled. If you still want to enable streaming, you need to polyfill those.

You will also need to override the default fetch in the `httpBatchStreamLink` configuration options with one that can stream. In the below example we will be using [`react-native-fetch-api`](https://github.com/react-native-community/fetch), which streams text when you pass its `reactNative` option. On Expo, you can pass `fetch` from [`expo/fetch`](https://docs.expo.dev/versions/latest/sdk/expo/) instead, which streams without extra options.

```ts
import { httpBatchStreamLink } from '@trpc/client';
import { fetch } from 'react-native-fetch-api';

httpBatchStreamLink({
  fetch: (url, opts) =>
    fetch(url, {
      ...opts,
      reactNative: { textStreaming: true },
    }),
  url: 'http://localhost:3000',
});
```

## Compatibility (server-side)

> [!WARNING] AWS Lambda
> `httpBatchStreamLink` only supported on AWS Lambda when your [infrastructure is set up](https://trpc.io/docs/server/adapters/aws-lambda#aws-lambda-response-streaming-adapter) for streaming responses. If not this Link will simply behave like a regular `httpBatchLink`.


> [!WARNING] Cloudflare Workers
> You need to enable the `ReadableStream` API through a feature flag: [`streams_enable_constructors`](https://developers.cloudflare.com/workers/platform/compatibility-dates#streams-constructors).


## Reference

You can check out the source code for this link on [GitHub.](https://github.com/trpc/trpc/blob/main/packages/client/src/links/httpBatchStreamLink.ts)

## Configure JSONL streaming options

When setting up your root config, you can pass in a `jsonl` option to configure ping behavior and the response `content-type` for streamed batch responses.

```ts no-check
import { initTRPC } from '@trpc/server';

const t = initTRPC.create({
  jsonl: {
    pingMs: 1000,
    contentType: 'application/jsonl',
  },
});
```

The default `contentType` remains `application/json` for backwards compatibility. Use `application/jsonl` if you want the response header to match the `trpc-accept` / `Accept` header sent by `httpBatchStreamLink`, or choose another value that works better with your tooling.
