Skip to content

Commit 41af48d

Browse files
committed
Add a guide about streaming
1 parent ff39205 commit 41af48d

File tree

2 files changed

+159
-53
lines changed

2 files changed

+159
-53
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
---
2+
title: Streaming Data from Server Functions
3+
---
4+
5+
Streaming data from the server has become very popular thanks to the rise of AI apps. Luckily, it's a pretty easy task with TanStack Start, and what's even better: the streamed data is typed!
6+
7+
The two most popular ways of streaming data from server functions are using `ReadableStream`-s or async generators.
8+
9+
You can see how to implement both in the [Streaming Data From Server Functions example](https://github.com/TanStack/router/tree/main/examples/react/start-streaming-data-from-server-functions).
10+
11+
## Typed Readable Streams
12+
13+
Here's an example for a server function that streams an array of messages to the client in a type-safe manner:
14+
15+
```ts
16+
type Message = {
17+
content: string;
18+
};
19+
20+
/**
21+
This server function returns a `ReadableStream`
22+
that streams `Message` chunks to the client.
23+
*/
24+
const streamingResponseFn = createServerFn({
25+
method: "GET",
26+
}).handler(async () => {
27+
// These are the messages that you want to send as chunks to the client
28+
const messages: Message[] = generateMessages();
29+
30+
// This `ReadableStream` is typed, so each
31+
// will be of type `Message`.
32+
const stream = new ReadableStream<Message>({
33+
async start(controller) {
34+
for (const message of messages) {
35+
// Send the message
36+
controller.enqueue(message);
37+
}
38+
controller.close();
39+
},
40+
});
41+
42+
return stream;
43+
});
44+
```
45+
46+
When you consume this stream from the client, the streamed chunks will be properly typed:
47+
48+
```ts
49+
const [message, setMessage] = useState("");
50+
51+
const getTypedReadableStreamResponse = useCallback(async () => {
52+
const response = await streamingResponseFn();
53+
54+
if (!response) {
55+
return;
56+
}
57+
58+
const reader = response.getReader();
59+
let done = false;
60+
while (!done) {
61+
const { value, done: doneReading } = await reader.read();
62+
done = doneReading;
63+
if (value) {
64+
// Notice how we know the value of `chunk` (`Message | undefined`)
65+
// here, because it's coming from the typed `ReadableStream`
66+
const chunk = value.content;
67+
setMessage((prev) => prev + chunk);
68+
}
69+
}
70+
}, []);
71+
```
72+
73+
## Async Generators in Server Functions
74+
75+
A much cleaner approach with the same results is to use an async generator function:
76+
77+
```ts
78+
const streamingWithAnAsyncGeneratorFn = createServerFn().handler(
79+
async function* () {
80+
const messages = generateMessages();
81+
for (const msg of messages) {
82+
// Notice how we defined the type of the streamed chunks
83+
// in the generic passed down the Promise constructor
84+
yield new Promise<Message>(async (r) => {
85+
// Send the message
86+
return r(msg);
87+
});
88+
}
89+
}
90+
);
91+
```
92+
93+
The client side code will also be leaner:
94+
95+
```ts
96+
const getResponseFromTheAsyncGenerator = useCallback(async () => {
97+
for await (const msg of await streamingWithAnAsyncGeneratorFn()) {
98+
const chunk = msg.content;
99+
setMessages((prev) => prev + chunk);
100+
}
101+
}, []);
102+
```

docs/start/framework/react/server-functions.md

Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ title: Server Functions
88
Server functions let you define server-only logic that can be called from anywhere in your application - loaders, components, hooks, or other server functions. They run on the server but can be invoked from client code seamlessly.
99

1010
```tsx
11-
import { createServerFn } from '@tanstack/react-start'
11+
import { createServerFn } from "@tanstack/react-start";
1212

1313
export const getServerTime = createServerFn().handler(async () => {
1414
// This runs only on the server
15-
return new Date().toISOString()
16-
})
15+
return new Date().toISOString();
16+
});
1717

1818
// Call from anywhere - components, loaders, hooks, etc.
19-
const time = await getServerTime()
19+
const time = await getServerTime();
2020
```
2121

2222
Server functions provide server capabilities (database access, environment variables, file system) while maintaining type safety across the network boundary.
@@ -26,18 +26,18 @@ Server functions provide server capabilities (database access, environment varia
2626
Server functions are created with `createServerFn()` and can specify HTTP method:
2727

2828
```tsx
29-
import { createServerFn } from '@tanstack/react-start'
29+
import { createServerFn } from "@tanstack/react-start";
3030

3131
// GET request (default)
3232
export const getData = createServerFn().handler(async () => {
33-
return { message: 'Hello from server!' }
34-
})
33+
return { message: "Hello from server!" };
34+
});
3535

3636
// POST request
37-
export const saveData = createServerFn({ method: 'POST' }).handler(async () => {
37+
export const saveData = createServerFn({ method: "POST" }).handler(async () => {
3838
// Server-only logic
39-
return { success: true }
40-
})
39+
return { success: true };
40+
});
4141
```
4242

4343
## Where to Call Server Functions
@@ -51,18 +51,18 @@ Call server functions from:
5151

5252
```tsx
5353
// In a route loader
54-
export const Route = createFileRoute('/posts')({
54+
export const Route = createFileRoute("/posts")({
5555
loader: () => getPosts(),
56-
})
56+
});
5757

5858
// In a component
5959
function PostList() {
60-
const getPosts = useServerFn(getServerPosts)
60+
const getPosts = useServerFn(getServerPosts);
6161

6262
const { data } = useQuery({
63-
queryKey: ['posts'],
63+
queryKey: ["posts"],
6464
queryFn: () => getPosts(),
65-
})
65+
});
6666
}
6767
```
6868

@@ -73,58 +73,58 @@ Server functions accept a single `data` parameter. Since they cross the network
7373
### Basic Parameters
7474

7575
```tsx
76-
import { createServerFn } from '@tanstack/react-start'
76+
import { createServerFn } from "@tanstack/react-start";
7777

78-
export const greetUser = createServerFn({ method: 'GET' })
78+
export const greetUser = createServerFn({ method: "GET" })
7979
.inputValidator((data: { name: string }) => data)
8080
.handler(async ({ data }) => {
81-
return `Hello, ${data.name}!`
82-
})
81+
return `Hello, ${data.name}!`;
82+
});
8383

84-
await greetUser({ data: { name: 'John' } })
84+
await greetUser({ data: { name: "John" } });
8585
```
8686

8787
### Validation with Zod
8888

8989
For robust validation, use schema libraries like Zod:
9090

9191
```tsx
92-
import { createServerFn } from '@tanstack/react-start'
93-
import { z } from 'zod'
92+
import { createServerFn } from "@tanstack/react-start";
93+
import { z } from "zod";
9494

9595
const UserSchema = z.object({
9696
name: z.string().min(1),
9797
age: z.number().min(0),
98-
})
98+
});
9999

100-
export const createUser = createServerFn({ method: 'POST' })
100+
export const createUser = createServerFn({ method: "POST" })
101101
.inputValidator(UserSchema)
102102
.handler(async ({ data }) => {
103103
// data is fully typed and validated
104-
return `Created user: ${data.name}, age ${data.age}`
105-
})
104+
return `Created user: ${data.name}, age ${data.age}`;
105+
});
106106
```
107107

108108
### Form Data
109109

110110
Handle form submissions with FormData:
111111

112112
```tsx
113-
export const submitForm = createServerFn({ method: 'POST' })
113+
export const submitForm = createServerFn({ method: "POST" })
114114
.inputValidator((data) => {
115115
if (!(data instanceof FormData)) {
116-
throw new Error('Expected FormData')
116+
throw new Error("Expected FormData");
117117
}
118118

119119
return {
120-
name: data.get('name')?.toString() || '',
121-
email: data.get('email')?.toString() || '',
122-
}
120+
name: data.get("name")?.toString() || "",
121+
email: data.get("email")?.toString() || "",
122+
};
123123
})
124124
.handler(async ({ data }) => {
125125
// Process form data
126-
return { success: true }
127-
})
126+
return { success: true };
127+
});
128128
```
129129

130130
## Error Handling & Redirects
@@ -134,20 +134,20 @@ Server functions can throw errors, redirects, and not-found responses that are h
134134
### Basic Errors
135135

136136
```tsx
137-
import { createServerFn } from '@tanstack/react-start'
137+
import { createServerFn } from "@tanstack/react-start";
138138

139139
export const riskyFunction = createServerFn().handler(async () => {
140140
if (Math.random() > 0.5) {
141-
throw new Error('Something went wrong!')
141+
throw new Error("Something went wrong!");
142142
}
143-
return { success: true }
144-
})
143+
return { success: true };
144+
});
145145

146146
// Errors are serialized to the client
147147
try {
148-
await riskyFunction()
148+
await riskyFunction();
149149
} catch (error) {
150-
console.log(error.message) // "Something went wrong!"
150+
console.log(error.message); // "Something went wrong!"
151151
}
152152
```
153153

@@ -156,39 +156,39 @@ try {
156156
Use redirects for authentication, navigation, etc:
157157

158158
```tsx
159-
import { createServerFn } from '@tanstack/react-start'
160-
import { redirect } from '@tanstack/react-router'
159+
import { createServerFn } from "@tanstack/react-start";
160+
import { redirect } from "@tanstack/react-router";
161161

162162
export const requireAuth = createServerFn().handler(async () => {
163-
const user = await getCurrentUser()
163+
const user = await getCurrentUser();
164164

165165
if (!user) {
166-
throw redirect({ to: '/login' })
166+
throw redirect({ to: "/login" });
167167
}
168168

169-
return user
170-
})
169+
return user;
170+
});
171171
```
172172

173173
### Not Found
174174

175175
Throw not-found errors for missing resources:
176176

177177
```tsx
178-
import { createServerFn } from '@tanstack/react-start'
179-
import { notFound } from '@tanstack/react-router'
178+
import { createServerFn } from "@tanstack/react-start";
179+
import { notFound } from "@tanstack/react-router";
180180

181181
export const getPost = createServerFn()
182182
.inputValidator((data: { id: string }) => data)
183183
.handler(async ({ data }) => {
184-
const post = await db.findPost(data.id)
184+
const post = await db.findPost(data.id);
185185

186186
if (!post) {
187-
throw notFound()
187+
throw notFound();
188188
}
189189

190-
return post
191-
})
190+
return post;
191+
});
192192
```
193193

194194
## Advanced Topics
@@ -204,9 +204,13 @@ Access request headers, cookies, and response customization:
204204
- `setResponseHeader()` - Set custom response headers
205205
- `setResponseStatus()` - Custom status codes
206206

207-
### Streaming & Raw Responses
207+
### Streaming
208+
209+
Stream typed data from server functions to the client. See the [Streaming Data from Server Functions guide ](../guide/streaming-data-from-server-functions).
210+
211+
### Raw Responses
208212

209-
Return `Response` objects for streaming, binary data, or custom content types.
213+
Return `Response` objects binary data, or custom content types.
210214

211215
### Progressive Enhancement
212216

0 commit comments

Comments
 (0)