0
# Error Handling
1
2
Comprehensive error handling system with structured error information and type guards for error identification. The tRPC client provides detailed error information including server responses, network issues, and client-side problems.
3
4
## Capabilities
5
6
### TRPCClientError
7
8
Main error class for all tRPC client operations, extending the standard JavaScript Error with structured error data and metadata.
9
10
```typescript { .api }
11
/**
12
* Main error class for tRPC client operations
13
* Extends Error with structured error data and metadata
14
*/
15
class TRPCClientError<TRouterOrProcedure extends InferrableClientTypes> extends Error {
16
/** Error message */
17
readonly message: string;
18
19
/** Structured error data from server */
20
readonly shape: Maybe<inferErrorShape<TRouterOrProcedure>>;
21
22
/** Error-specific data payload */
23
readonly data: Maybe<inferErrorShape<TRouterOrProcedure>['data']>;
24
25
/** Original error cause */
26
readonly cause: Error;
27
28
/** Additional metadata (e.g., HTTP response details) */
29
meta: Record<string, unknown>;
30
31
constructor(
32
message: string,
33
opts?: {
34
result?: Maybe<TRPCErrorResponse<inferErrorShape<TRouterOrProcedure>>>;
35
cause?: Error;
36
meta?: Record<string, unknown>;
37
}
38
);
39
40
/**
41
* Creates TRPCClientError from various error sources
42
* @param cause - Error source (Error, TRPCErrorResponse, or object)
43
* @param opts - Additional options including metadata
44
* @returns Properly formatted TRPCClientError instance
45
*/
46
static from<TRouterOrProcedure extends InferrableClientTypes>(
47
cause: Error | TRPCErrorResponse | object,
48
opts?: { meta?: Record<string, unknown> }
49
): TRPCClientError<TRouterOrProcedure>;
50
}
51
52
/** Server error response structure */
53
interface TRPCErrorResponse<TShape> {
54
error: {
55
code: number;
56
message: string;
57
data?: TShape['data'];
58
};
59
}
60
61
/** Utility type for nullable values */
62
type Maybe<T> = T | null | undefined;
63
64
/** Infer error shape from router or procedure type */
65
type inferErrorShape<TInferrable extends InferrableClientTypes> =
66
inferClientTypes<TInferrable>['errorShape'];
67
```
68
69
**Usage Examples:**
70
71
```typescript
72
import { TRPCClientError, isTRPCClientError } from "@trpc/client";
73
74
// Basic error handling
75
try {
76
const user = await client.user.getById.query({ id: 999 });
77
} catch (error) {
78
if (error instanceof TRPCClientError) {
79
console.log("tRPC Error:", error.message);
80
console.log("Error code:", error.data?.code);
81
console.log("Error details:", error.data);
82
83
// Access HTTP metadata if available
84
if (error.meta?.response) {
85
console.log("HTTP status:", error.meta.response.status);
86
}
87
}
88
}
89
90
// Create error from different sources
91
const networkError = new Error("Network timeout");
92
const trpcError = TRPCClientError.from(networkError, {
93
meta: { source: "network" }
94
});
95
96
// Server error response
97
const serverErrorResponse = {
98
error: {
99
code: -32602,
100
message: "Invalid params",
101
data: { field: "id", issue: "required" }
102
}
103
};
104
const validationError = TRPCClientError.from(serverErrorResponse);
105
```
106
107
### isTRPCClientError
108
109
Type guard function to safely check if an unknown error is a TRPCClientError instance.
110
111
```typescript { .api }
112
/**
113
* Type guard to check if error is TRPCClientError instance
114
* @param cause - Unknown error value to check
115
* @returns True if cause is TRPCClientError, false otherwise
116
*/
117
function isTRPCClientError<TInferrable extends InferrableClientTypes>(
118
cause: unknown
119
): cause is TRPCClientError<TInferrable>;
120
```
121
122
**Usage Examples:**
123
124
```typescript
125
// Safe error type checking
126
function handleError(error: unknown) {
127
if (isTRPCClientError(error)) {
128
// TypeScript knows this is TRPCClientError
129
console.log("tRPC error:", error.message);
130
console.log("Error shape:", error.shape);
131
console.log("Error data:", error.data);
132
133
// Handle specific error codes
134
if (error.data?.code === "UNAUTHORIZED") {
135
redirectToLogin();
136
} else if (error.data?.code === "FORBIDDEN") {
137
showAccessDeniedMessage();
138
}
139
} else if (error instanceof Error) {
140
// Standard JavaScript error
141
console.log("Standard error:", error.message);
142
} else {
143
// Unknown error type
144
console.log("Unknown error:", error);
145
}
146
}
147
148
// Use in async error handling
149
async function fetchUserData(id: number) {
150
try {
151
return await client.user.getById.query({ id });
152
} catch (error) {
153
if (isTRPCClientError(error)) {
154
// Handle tRPC-specific errors
155
throw new Error(`Failed to fetch user: ${error.message}`);
156
}
157
// Re-throw other errors
158
throw error;
159
}
160
}
161
```
162
163
### Error Categories
164
165
Different types of errors that can occur in tRPC client operations.
166
167
```typescript { .api }
168
/** Network and connection errors */
169
interface NetworkError {
170
type: 'network';
171
cause: Error;
172
meta?: {
173
url?: string;
174
method?: string;
175
timeout?: boolean;
176
};
177
}
178
179
/** HTTP response errors */
180
interface HTTPError {
181
type: 'http';
182
status: number;
183
statusText: string;
184
meta: {
185
response: Response;
186
responseJSON?: any;
187
};
188
}
189
190
/** Server-side validation or business logic errors */
191
interface ServerError {
192
type: 'server';
193
code: string | number;
194
message: string;
195
data?: Record<string, any>;
196
}
197
198
/** Client-side validation or configuration errors */
199
interface ClientError {
200
type: 'client';
201
cause: Error;
202
operation?: string;
203
}
204
```
205
206
**Error Category Examples:**
207
208
```typescript
209
// Network error handling
210
try {
211
const result = await client.posts.getAll.query();
212
} catch (error) {
213
if (isTRPCClientError(error)) {
214
// Check if it's a network error
215
if (error.cause?.name === 'TypeError' && error.cause?.message.includes('fetch')) {
216
console.log("Network error - check connection");
217
showOfflineMessage();
218
return;
219
}
220
221
// Check HTTP status codes
222
const httpStatus = error.meta?.response?.status;
223
if (httpStatus === 500) {
224
console.log("Server error - try again later");
225
showRetryButton();
226
} else if (httpStatus === 404) {
227
console.log("Resource not found");
228
showNotFoundMessage();
229
}
230
231
// Check tRPC error codes
232
if (error.data?.code === 'TIMEOUT') {
233
console.log("Request timeout - try again");
234
} else if (error.data?.code === 'PARSE_ERROR') {
235
console.log("Invalid request format");
236
}
237
}
238
}
239
```
240
241
### HTTP Error Details
242
243
Access detailed HTTP response information from failed requests.
244
245
```typescript { .api }
246
/** HTTP response metadata structure */
247
interface HTTPResponseMeta {
248
response: Response;
249
responseJSON?: any;
250
request?: {
251
url: string;
252
method: string;
253
headers: Record<string, string>;
254
};
255
}
256
```
257
258
**HTTP Error Examples:**
259
260
```typescript
261
// Detailed HTTP error analysis
262
try {
263
const user = await client.user.update.mutate({ id: 1, name: "New Name" });
264
} catch (error) {
265
if (isTRPCClientError(error) && error.meta?.response) {
266
const response = error.meta.response as Response;
267
268
console.log("HTTP Status:", response.status);
269
console.log("Status Text:", response.statusText);
270
console.log("Response Headers:", Object.fromEntries(response.headers.entries()));
271
272
// Handle specific HTTP status codes
273
switch (response.status) {
274
case 400:
275
console.log("Bad Request - check input data");
276
if (error.data?.validationErrors) {
277
displayValidationErrors(error.data.validationErrors);
278
}
279
break;
280
case 401:
281
console.log("Unauthorized - refresh token");
282
await refreshAuthToken();
283
break;
284
case 403:
285
console.log("Forbidden - insufficient permissions");
286
showPermissionError();
287
break;
288
case 404:
289
console.log("Not Found - resource doesn't exist");
290
redirectToNotFound();
291
break;
292
case 429:
293
console.log("Rate Limited - wait before retry");
294
const retryAfter = response.headers.get('Retry-After');
295
scheduleRetry(parseInt(retryAfter || '60'));
296
break;
297
case 500:
298
console.log("Server Error - report to support");
299
reportServerError(error);
300
break;
301
}
302
303
// Access raw response body if available
304
if (error.meta.responseJSON) {
305
console.log("Response Body:", error.meta.responseJSON);
306
}
307
}
308
}
309
```
310
311
### Validation Error Handling
312
313
Handle structured validation errors from input/output schema validation.
314
315
```typescript { .api }
316
/** Validation error structure */
317
interface ValidationError {
318
code: string;
319
message: string;
320
path: (string | number)[];
321
expected?: any;
322
received?: any;
323
}
324
325
/** Validation error response */
326
interface ValidationErrorResponse {
327
code: 'BAD_REQUEST' | 'VALIDATION_ERROR';
328
message: string;
329
validationErrors: ValidationError[];
330
}
331
```
332
333
**Validation Error Examples:**
334
335
```typescript
336
// Handle input validation errors
337
try {
338
const user = await client.user.create.mutate({
339
name: "", // Invalid empty name
340
email: "invalid-email", // Invalid email format
341
age: -5, // Invalid negative age
342
});
343
} catch (error) {
344
if (isTRPCClientError(error) && error.data?.code === 'BAD_REQUEST') {
345
const validationErrors = error.data.validationErrors as ValidationError[];
346
347
validationErrors.forEach((err) => {
348
const fieldPath = err.path.join('.');
349
console.log(`Validation error in ${fieldPath}: ${err.message}`);
350
351
// Show field-specific errors in UI
352
if (fieldPath === 'email') {
353
showFieldError('email', 'Please enter a valid email address');
354
} else if (fieldPath === 'name') {
355
showFieldError('name', 'Name is required');
356
} else if (fieldPath === 'age') {
357
showFieldError('age', 'Age must be a positive number');
358
}
359
});
360
}
361
}
362
363
// Handle output validation errors (server response doesn't match schema)
364
try {
365
const posts = await client.posts.getAll.query();
366
} catch (error) {
367
if (isTRPCClientError(error) && error.data?.code === 'INTERNAL_SERVER_ERROR') {
368
if (error.message.includes('output validation')) {
369
console.error("Server returned invalid data format");
370
// Report to monitoring service
371
reportDataIntegrityIssue(error);
372
}
373
}
374
}
375
```
376
377
### Error Recovery Strategies
378
379
Common patterns for recovering from different types of errors.
380
381
```typescript { .api }
382
/** Retry configuration options */
383
interface RetryOptions {
384
maxAttempts: number;
385
delayMs: number | ((attempt: number) => number);
386
retryIf: (error: TRPCClientError<any>) => boolean;
387
}
388
```
389
390
**Error Recovery Examples:**
391
392
```typescript
393
// Exponential backoff retry
394
async function withRetry<T>(
395
operation: () => Promise<T>,
396
options: RetryOptions
397
): Promise<T> {
398
let lastError: TRPCClientError<any>;
399
400
for (let attempt = 0; attempt < options.maxAttempts; attempt++) {
401
try {
402
return await operation();
403
} catch (error) {
404
if (!isTRPCClientError(error) || !options.retryIf(error)) {
405
throw error;
406
}
407
408
lastError = error;
409
410
if (attempt < options.maxAttempts - 1) {
411
const delay = typeof options.delayMs === 'function'
412
? options.delayMs(attempt)
413
: options.delayMs;
414
await new Promise(resolve => setTimeout(resolve, delay));
415
}
416
}
417
}
418
419
throw lastError;
420
}
421
422
// Usage with retry strategy
423
const userData = await withRetry(
424
() => client.user.getById.query({ id: 1 }),
425
{
426
maxAttempts: 3,
427
delayMs: (attempt) => Math.min(1000 * Math.pow(2, attempt), 10000),
428
retryIf: (error) => {
429
// Retry on network errors and 5xx server errors
430
const isNetworkError = error.cause?.name === 'TypeError';
431
const isServerError = error.meta?.response?.status >= 500;
432
return isNetworkError || isServerError;
433
}
434
}
435
);
436
437
// Graceful degradation
438
async function getUserWithFallback(id: number) {
439
try {
440
return await client.user.getById.query({ id });
441
} catch (error) {
442
if (isTRPCClientError(error)) {
443
console.warn("Failed to fetch user, using cached data:", error.message);
444
return getCachedUser(id);
445
}
446
throw error;
447
}
448
}
449
450
// Circuit breaker pattern
451
class CircuitBreaker {
452
private failures = 0;
453
private readonly threshold = 5;
454
private readonly timeoutMs = 30000;
455
private lastFailTime = 0;
456
457
async execute<T>(operation: () => Promise<T>): Promise<T> {
458
if (this.isOpen()) {
459
throw new Error("Circuit breaker is open");
460
}
461
462
try {
463
const result = await operation();
464
this.onSuccess();
465
return result;
466
} catch (error) {
467
this.onFailure();
468
throw error;
469
}
470
}
471
472
private isOpen(): boolean {
473
return this.failures >= this.threshold &&
474
(Date.now() - this.lastFailTime) < this.timeoutMs;
475
}
476
477
private onSuccess(): void {
478
this.failures = 0;
479
}
480
481
private onFailure(): void {
482
this.failures++;
483
this.lastFailTime = Date.now();
484
}
485
}
486
```
487
488
### Error Monitoring and Reporting
489
490
Integration with error monitoring services for production error tracking.
491
492
```typescript { .api }
493
/** Error context for monitoring services */
494
interface ErrorContext {
495
operation: {
496
type: 'query' | 'mutation' | 'subscription';
497
path: string;
498
input?: any;
499
};
500
user?: {
501
id: string;
502
email?: string;
503
};
504
session?: {
505
id: string;
506
duration: number;
507
};
508
environment: {
509
userAgent: string;
510
url: string;
511
timestamp: number;
512
};
513
}
514
```
515
516
**Error Monitoring Examples:**
517
518
```typescript
519
// Global error handler for monitoring
520
function setupErrorMonitoring() {
521
const originalConsoleError = console.error;
522
523
console.error = (...args) => {
524
args.forEach(arg => {
525
if (isTRPCClientError(arg)) {
526
reportErrorToMonitoring(arg);
527
}
528
});
529
originalConsoleError(...args);
530
};
531
}
532
533
function reportErrorToMonitoring(error: TRPCClientError<any>) {
534
const context: ErrorContext = {
535
operation: {
536
type: error.meta?.operationType || 'unknown',
537
path: error.meta?.operationPath || 'unknown',
538
input: error.meta?.operationInput,
539
},
540
user: getCurrentUser(),
541
session: getCurrentSession(),
542
environment: {
543
userAgent: navigator.userAgent,
544
url: window.location.href,
545
timestamp: Date.now(),
546
},
547
};
548
549
// Send to monitoring service (Sentry, Datadog, etc.)
550
monitoringService.captureException(error, context);
551
}
552
553
// Custom error boundary for React applications
554
class TRPCErrorBoundary extends React.Component {
555
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
556
if (isTRPCClientError(error)) {
557
reportErrorToMonitoring(error);
558
559
// Show user-friendly error message
560
this.setState({
561
hasError: true,
562
errorType: 'trpc',
563
canRetry: this.isRetryableError(error),
564
});
565
}
566
}
567
568
private isRetryableError(error: TRPCClientError<any>): boolean {
569
const httpStatus = error.meta?.response?.status;
570
return httpStatus === 429 || httpStatus >= 500 ||
571
error.cause?.name === 'TypeError';
572
}
573
}
574
```