0
# RPC System
1
2
The RPC (Remote Procedure Call) system provides type-safe client-server communication with automatic serialization and request handling. It enables seamless function calls between client and server environments with full type safety and error handling.
3
4
## Capabilities
5
6
### Create Client RPC
7
8
Creates client-side RPC functions that can communicate with server-side endpoints.
9
10
```typescript { .api }
11
/**
12
* Creates a client-side RPC function for server communication
13
* @param functionId - Unique identifier for the RPC function
14
* @param fetcher - Function that handles the actual HTTP request
15
* @returns ClientRpc instance for making server calls
16
*/
17
function createClientRpc(
18
functionId: string,
19
fetcher: (...args: any[]) => Promise<any>
20
): ClientRpc;
21
22
interface ClientRpc {
23
(...args: any[]): Promise<any>;
24
functionId: string;
25
url: string;
26
}
27
```
28
29
**Usage Examples:**
30
31
```typescript
32
import { createClientRpc } from "@tanstack/react-start/client-rpc";
33
34
// Basic RPC function
35
const getUserRpc = createClientRpc(
36
"getUser",
37
async (id: string) => {
38
const response = await fetch(`/api/functions/getUser`, {
39
method: "POST",
40
headers: { "Content-Type": "application/json" },
41
body: JSON.stringify({ id })
42
});
43
return response.json();
44
}
45
);
46
47
// Use in React component
48
function UserProfile({ userId }: { userId: string }) {
49
const [user, setUser] = useState(null);
50
51
useEffect(() => {
52
getUserRpc(userId).then(setUser);
53
}, [userId]);
54
55
return user ? <div>{user.name}</div> : <div>Loading...</div>;
56
}
57
58
// Advanced RPC with error handling
59
const createUserRpc = createClientRpc(
60
"createUser",
61
async (userData: { name: string; email: string }) => {
62
try {
63
const response = await fetch("/api/functions/createUser", {
64
method: "POST",
65
headers: { "Content-Type": "application/json" },
66
body: JSON.stringify(userData)
67
});
68
69
if (!response.ok) {
70
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
71
}
72
73
return response.json();
74
} catch (error) {
75
console.error("RPC call failed:", error);
76
throw error;
77
}
78
}
79
);
80
```
81
82
### Create Server RPC
83
84
Creates server-side RPC functions that can be called from client code.
85
86
```typescript { .api }
87
/**
88
* Creates a server-side RPC function with split import functionality
89
* @param functionId - Unique identifier for the RPC function
90
* @param splitImportFn - Function that handles dynamic imports and execution
91
* @returns ServerRpc instance with URL and function metadata
92
*/
93
function createServerRpc(
94
functionId: string,
95
splitImportFn: (...args: any[]) => any
96
): ServerRpc;
97
98
interface ServerRpc {
99
(...args: any[]): any;
100
url: string;
101
functionId: string;
102
[TSS_SERVER_FUNCTION]: true;
103
}
104
```
105
106
**Usage Examples:**
107
108
```typescript
109
import { createServerRpc } from "@tanstack/react-start/server-rpc";
110
111
// Basic server RPC
112
const getUserServerRpc = createServerRpc(
113
"getUser",
114
async (id: string) => {
115
// Dynamic import for server-only code
116
const { db } = await import("./database");
117
return db.user.findUnique({ where: { id } });
118
}
119
);
120
121
// RPC with complex business logic
122
const processOrderServerRpc = createServerRpc(
123
"processOrder",
124
async (orderData: {
125
items: Array<{ id: string; quantity: number }>;
126
customerId: string;
127
paymentMethod: string;
128
}) => {
129
// Split imports for server-only dependencies
130
const [
131
{ db },
132
{ paymentProcessor },
133
{ inventoryManager },
134
{ emailService }
135
] = await Promise.all([
136
import("./database"),
137
import("./payment"),
138
import("./inventory"),
139
import("./email")
140
]);
141
142
// Process order with full server-side logic
143
const order = await db.order.create({
144
data: {
145
customerId: orderData.customerId,
146
items: {
147
create: orderData.items.map(item => ({
148
productId: item.id,
149
quantity: item.quantity
150
}))
151
}
152
}
153
});
154
155
// Process payment
156
const payment = await paymentProcessor.charge({
157
amount: order.total,
158
method: orderData.paymentMethod,
159
customerId: orderData.customerId
160
});
161
162
// Update inventory
163
await inventoryManager.decrementStock(orderData.items);
164
165
// Send confirmation email
166
await emailService.sendOrderConfirmation(orderData.customerId, order);
167
168
return { order, payment };
169
}
170
);
171
```
172
173
### Server Function Fetcher
174
175
Utilities for handling server function requests and responses.
176
177
```typescript { .api }
178
/**
179
* Server function fetcher for handling RPC requests
180
* @param url - The URL to fetch from
181
* @param options - Fetch options including method and body
182
* @returns Promise resolving to the response
183
*/
184
interface ServerFnFetcher {
185
(url: string, options: ServerFnFetchOptions): Promise<Response>;
186
}
187
188
interface ServerFnFetchOptions {
189
method: string;
190
headers?: HeadersInit;
191
body?: BodyInit;
192
}
193
```
194
195
## RPC Configuration and Environment
196
197
### Environment Variables
198
199
The RPC system uses environment variables for configuration:
200
201
```typescript
202
// Server configuration
203
process.env.TSS_APP_BASE = "/"; // Base path for the application
204
process.env.TSS_SERVER_FN_BASE = "api/functions"; // Base path for server functions
205
```
206
207
### URL Generation
208
209
RPC functions automatically generate URLs based on configuration:
210
211
```typescript
212
// Generated URL structure
213
const baseUrl = `${TSS_APP_BASE}/${TSS_SERVER_FN_BASE}/`;
214
const functionUrl = baseUrl + functionId;
215
216
// Example: /api/functions/getUser
217
```
218
219
## Advanced Usage Patterns
220
221
### RPC with Authentication
222
223
```typescript
224
import { createClientRpc } from "@tanstack/react-start/client-rpc";
225
226
// Authenticated RPC calls
227
const createAuthenticatedRpc = (functionId: string) =>
228
createClientRpc(functionId, async (...args) => {
229
const token = localStorage.getItem("auth-token");
230
231
const response = await fetch(`/api/functions/${functionId}`, {
232
method: "POST",
233
headers: {
234
"Content-Type": "application/json",
235
"Authorization": `Bearer ${token}`
236
},
237
body: JSON.stringify(args)
238
});
239
240
if (response.status === 401) {
241
// Handle authentication error
242
window.location.href = "/login";
243
throw new Error("Authentication required");
244
}
245
246
return response.json();
247
});
248
249
const getUserRpc = createAuthenticatedRpc("getUser");
250
const updateUserRpc = createAuthenticatedRpc("updateUser");
251
```
252
253
### RPC with Caching
254
255
```typescript
256
import { createClientRpc } from "@tanstack/react-start/client-rpc";
257
258
// Cached RPC calls
259
const cache = new Map();
260
261
const createCachedRpc = (functionId: string, cacheTTL = 5 * 60 * 1000) =>
262
createClientRpc(functionId, async (...args) => {
263
const cacheKey = `${functionId}:${JSON.stringify(args)}`;
264
const cached = cache.get(cacheKey);
265
266
if (cached && Date.now() - cached.timestamp < cacheTTL) {
267
return cached.data;
268
}
269
270
const response = await fetch(`/api/functions/${functionId}`, {
271
method: "POST",
272
headers: { "Content-Type": "application/json" },
273
body: JSON.stringify(args)
274
});
275
276
const data = await response.json();
277
cache.set(cacheKey, { data, timestamp: Date.now() });
278
279
return data;
280
});
281
282
const getUserRpc = createCachedRpc("getUser", 10 * 60 * 1000); // 10 minute cache
283
```
284
285
### Server RPC with Validation
286
287
```typescript
288
import { createServerRpc } from "@tanstack/react-start/server-rpc";
289
290
const validateUserInput = (data: unknown) => {
291
if (!data || typeof data !== "object") {
292
throw new Error("Invalid input");
293
}
294
295
const { name, email } = data as any;
296
if (!name || typeof name !== "string") {
297
throw new Error("Name is required");
298
}
299
if (!email || !email.includes("@")) {
300
throw new Error("Valid email is required");
301
}
302
303
return { name, email };
304
};
305
306
const createUserServerRpc = createServerRpc(
307
"createUser",
308
async (input: unknown) => {
309
// Validate input
310
const userData = validateUserInput(input);
311
312
// Import server dependencies
313
const { db } = await import("./database");
314
const { hashPassword } = await import("./crypto");
315
const { sendWelcomeEmail } = await import("./email");
316
317
// Create user with validated data
318
const user = await db.user.create({
319
data: {
320
name: userData.name,
321
email: userData.email,
322
passwordHash: await hashPassword("temporary")
323
}
324
});
325
326
// Send welcome email asynchronously
327
sendWelcomeEmail(user.email, user.name).catch(console.error);
328
329
return { user: { id: user.id, name: user.name, email: user.email } };
330
}
331
);
332
```
333
334
### RPC Error Handling
335
336
```typescript
337
import { createClientRpc } from "@tanstack/react-start/client-rpc";
338
339
const createRobustRpc = (functionId: string, maxRetries = 3) =>
340
createClientRpc(functionId, async (...args) => {
341
let lastError;
342
343
for (let attempt = 1; attempt <= maxRetries; attempt++) {
344
try {
345
const response = await fetch(`/api/functions/${functionId}`, {
346
method: "POST",
347
headers: { "Content-Type": "application/json" },
348
body: JSON.stringify(args)
349
});
350
351
if (!response.ok) {
352
if (response.status >= 500 && attempt < maxRetries) {
353
// Retry on server errors
354
await new Promise(resolve =>
355
setTimeout(resolve, Math.pow(2, attempt) * 1000)
356
);
357
continue;
358
}
359
360
const errorData = await response.json().catch(() => ({}));
361
throw new Error(errorData.message || `HTTP ${response.status}`);
362
}
363
364
return response.json();
365
} catch (error) {
366
lastError = error;
367
if (attempt === maxRetries) break;
368
369
// Exponential backoff for retries
370
await new Promise(resolve =>
371
setTimeout(resolve, Math.pow(2, attempt) * 1000)
372
);
373
}
374
}
375
376
throw lastError;
377
});
378
379
const getUserRpc = createRobustRpc("getUser");
380
```
381
382
## Types
383
384
```typescript { .api }
385
// RPC function types
386
interface ClientRpc {
387
(...args: any[]): Promise<any>;
388
functionId: string;
389
url: string;
390
}
391
392
interface ServerRpc {
393
(...args: any[]): any;
394
url: string;
395
functionId: string;
396
[TSS_SERVER_FUNCTION]: true;
397
}
398
399
// Server function marker
400
declare const TSS_SERVER_FUNCTION: unique symbol;
401
402
// Fetch options for server functions
403
interface ServerFnFetchOptions {
404
method: string;
405
headers?: HeadersInit;
406
body?: BodyInit;
407
}
408
409
// Server function fetcher type
410
interface ServerFnFetcher {
411
(url: string, options: ServerFnFetchOptions): Promise<Response>;
412
}
413
414
// RPC stream types
415
interface RscStream {
416
// React Server Component stream handling
417
read(): Promise<any>;
418
pipe(destination: any): any;
419
}
420
421
// Fetcher data types
422
interface FetcherData {
423
url: string;
424
method: string;
425
headers: Headers;
426
body?: any;
427
}
428
429
interface FetcherBaseOptions {
430
method?: string;
431
headers?: HeadersInit;
432
}
433
434
// Compiled fetcher types
435
interface CompiledFetcherFnOptions {
436
method: string;
437
headers?: HeadersInit;
438
body?: BodyInit;
439
}
440
441
interface CompiledFetcherFn {
442
(url: string, options: CompiledFetcherFnOptions): Promise<Response>;
443
}
444
445
interface Fetcher {
446
fn: CompiledFetcherFn;
447
options: CompiledFetcherFnOptions;
448
}
449
450
// Optional and required fetcher types
451
interface OptionalFetcher {
452
fetcher?: Fetcher;
453
}
454
455
interface RequiredFetcher {
456
fetcher: Fetcher;
457
}
458
```