0
# Error Handling
1
2
Structured error handling with PostgreSQL error details, custom error types, and response validation patterns.
3
4
## Capabilities
5
6
### PostgrestError Class
7
8
Custom error class that provides detailed information about PostgREST and PostgreSQL errors.
9
10
```typescript { .api }
11
/**
12
* Error format
13
* {@link https://postgrest.org/en/stable/api.html?highlight=options#errors-and-http-status-codes}
14
*/
15
class PostgrestError extends Error {
16
name: 'PostgrestError';
17
details: string;
18
hint: string;
19
code: string;
20
21
constructor(context: {
22
message: string;
23
details: string;
24
hint: string;
25
code: string;
26
});
27
}
28
```
29
30
**Usage Examples:**
31
32
```typescript
33
import { PostgrestClient, PostgrestError } from "@supabase/postgrest-js";
34
35
const client = new PostgrestClient("https://api.example.com");
36
37
// Handle PostgrestError specifically
38
const { data, error } = await client
39
.from("users")
40
.insert({ email: "duplicate@example.com" })
41
.select();
42
43
if (error) {
44
console.log("Error name:", error.name); // "PostgrestError"
45
console.log("Error message:", error.message); // Human-readable message
46
console.log("Error details:", error.details); // Technical details
47
console.log("Error hint:", error.hint); // Suggestion for fixing
48
console.log("Error code:", error.code); // PostgreSQL error code
49
50
// Handle specific error types
51
if (error.code === "23505") {
52
console.log("Duplicate key violation - record already exists");
53
}
54
}
55
56
// Error in try-catch block
57
try {
58
const { data } = await client
59
.from("restricted_table")
60
.select("*")
61
.throwOnError(); // Throws instead of returning error
62
} catch (error) {
63
if (error instanceof PostgrestError) {
64
console.log("PostgreSQL Error:", error.code, error.message);
65
console.log("Details:", error.details);
66
console.log("Hint:", error.hint);
67
} else {
68
console.log("Other error:", error);
69
}
70
}
71
```
72
73
### Response Types
74
75
Different response types for success and failure scenarios.
76
77
```typescript { .api }
78
/**
79
* Response format
80
* {@link https://github.com/supabase/supabase-js/issues/32}
81
*/
82
interface PostgrestResponseBase {
83
status: number;
84
statusText: string;
85
}
86
87
interface PostgrestResponseSuccess<T> extends PostgrestResponseBase {
88
error: null;
89
data: T;
90
count: number | null;
91
}
92
93
interface PostgrestResponseFailure extends PostgrestResponseBase {
94
error: PostgrestError;
95
data: null;
96
count: null;
97
}
98
99
type PostgrestSingleResponse<T> = PostgrestResponseSuccess<T> | PostgrestResponseFailure;
100
type PostgrestMaybeSingleResponse<T> = PostgrestSingleResponse<T | null>;
101
type PostgrestResponse<T> = PostgrestSingleResponse<T[]>;
102
```
103
104
**Usage Examples:**
105
106
```typescript
107
// Standard response handling
108
const response = await client
109
.from("users")
110
.select("*")
111
.eq("id", 123);
112
113
if (response.error) {
114
// Handle error case
115
console.log("Request failed:", response.error.message);
116
console.log("HTTP Status:", response.status, response.statusText);
117
// response.data is null, response.count is null
118
} else {
119
// Handle success case
120
console.log("Request succeeded:", response.data);
121
console.log("Record count:", response.count);
122
console.log("HTTP Status:", response.status, response.statusText);
123
// response.error is null
124
}
125
126
// Type-safe response handling
127
function handleUserResponse(response: PostgrestResponse<User>) {
128
if (response.error) {
129
// TypeScript knows this is PostgrestResponseFailure
130
console.error("Error code:", response.error.code);
131
return;
132
}
133
134
// TypeScript knows this is PostgrestResponseSuccess<User[]>
135
console.log("Users found:", response.data.length);
136
response.data.forEach(user => {
137
console.log("User:", user.name); // Fully typed
138
});
139
}
140
141
// Single response handling
142
const singleResponse = await client
143
.from("users")
144
.select("*")
145
.eq("id", 123)
146
.single();
147
148
if (singleResponse.error) {
149
console.log("User not found or multiple users found");
150
} else {
151
// singleResponse.data is User, not User[]
152
console.log("Found user:", singleResponse.data.name);
153
}
154
```
155
156
### Error Mode Control
157
158
Control whether errors are thrown or returned in the response.
159
160
```typescript { .api }
161
/**
162
* If there's an error with the query, throwOnError will reject the promise by
163
* throwing the error instead of returning it as part of a successful response.
164
* {@link https://github.com/supabase/supabase-js/issues/92}
165
*/
166
throwOnError(): this & PostgrestBuilder<ClientOptions, Result, true>;
167
```
168
169
**Usage Examples:**
170
171
```typescript
172
// Default behavior - errors returned in response
173
const { data, error } = await client
174
.from("users")
175
.select("*")
176
.eq("id", 999);
177
178
if (error) {
179
// Handle error manually
180
console.log("Error occurred:", error.message);
181
}
182
183
// Throw on error - errors thrown as exceptions
184
try {
185
const { data } = await client
186
.from("users")
187
.select("*")
188
.eq("id", 999)
189
.throwOnError(); // Will throw if error occurs
190
191
// No need to check for error - if we reach here, it succeeded
192
console.log("Success:", data);
193
} catch (error) {
194
// Handle thrown error
195
if (error instanceof PostgrestError) {
196
console.log("Database error:", error.message);
197
} else {
198
console.log("Network or other error:", error);
199
}
200
}
201
202
// Useful for promise chains
203
client
204
.from("users")
205
.insert({ name: "John Doe", email: "john@example.com" })
206
.select()
207
.throwOnError()
208
.then(({ data }) => {
209
// Only executes on success
210
console.log("Created user:", data);
211
return data;
212
})
213
.catch((error) => {
214
// Only executes on error
215
console.error("Failed to create user:", error.message);
216
});
217
```
218
219
### Common Error Patterns
220
221
Handle common PostgreSQL and PostgREST error scenarios.
222
223
**Usage Examples:**
224
225
```typescript
226
// Database constraint violations
227
const { data, error } = await client
228
.from("users")
229
.insert({ email: "existing@example.com" });
230
231
if (error) {
232
switch (error.code) {
233
case "23505": // unique_violation
234
console.log("Email already exists");
235
break;
236
case "23503": // foreign_key_violation
237
console.log("Referenced record doesn't exist");
238
break;
239
case "23514": // check_violation
240
console.log("Data doesn't meet constraint requirements");
241
break;
242
case "23502": // not_null_violation
243
console.log("Required field is missing");
244
break;
245
default:
246
console.log("Database error:", error.code, error.message);
247
}
248
}
249
250
// Permission and access errors
251
const { data: restrictedData, error: accessError } = await client
252
.from("admin_table")
253
.select("*");
254
255
if (accessError) {
256
switch (accessError.code) {
257
case "42501": // insufficient_privilege
258
console.log("Access denied - insufficient permissions");
259
break;
260
case "42P01": // undefined_table
261
console.log("Table does not exist or is not accessible");
262
break;
263
case "42703": // undefined_column
264
console.log("Column does not exist");
265
break;
266
default:
267
console.log("Access error:", accessError.message);
268
}
269
}
270
271
// Query syntax and validation errors
272
const { data: queryData, error: queryError } = await client
273
.from("users")
274
.select("invalid_syntax((()))");
275
276
if (queryError) {
277
if (queryError.code.startsWith("42")) {
278
console.log("SQL syntax or schema error:", queryError.message);
279
console.log("Hint:", queryError.hint);
280
}
281
}
282
```
283
284
### Network and Connection Errors
285
286
Handle network-level and connection errors.
287
288
**Usage Examples:**
289
290
```typescript
291
// Network timeout handling
292
const controller = new AbortController();
293
setTimeout(() => controller.abort(), 5000); // 5 second timeout
294
295
try {
296
const { data } = await client
297
.from("large_table")
298
.select("*")
299
.abortSignal(controller.signal)
300
.throwOnError();
301
} catch (error) {
302
if (error.name === 'AbortError') {
303
console.log("Request timed out");
304
} else if (error instanceof PostgrestError) {
305
console.log("Database error:", error.message);
306
} else {
307
console.log("Network error:", error.message);
308
}
309
}
310
311
// Connection error handling
312
const { data, error } = await client
313
.from("users")
314
.select("*");
315
316
if (error) {
317
// Check for network/connection errors vs database errors
318
if (error.code === '' && error.message.includes('fetch')) {
319
console.log("Network connection error");
320
// Maybe show retry option to user
321
} else if (error.code) {
322
console.log("Database error:", error.code, error.message);
323
} else {
324
console.log("Unknown error:", error.message);
325
}
326
}
327
328
// Retry logic for network errors
329
async function queryWithRetry(maxRetries = 3) {
330
for (let attempt = 0; attempt < maxRetries; attempt++) {
331
const { data, error } = await client
332
.from("users")
333
.select("*");
334
335
if (!error) {
336
return { data, error: null };
337
}
338
339
// Retry only on network errors, not database errors
340
if (error.code === '' && attempt < maxRetries - 1) {
341
console.log(`Attempt ${attempt + 1} failed, retrying...`);
342
await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
343
continue;
344
}
345
346
return { data: null, error };
347
}
348
}
349
```
350
351
### Error Context and Debugging
352
353
Extract detailed information for debugging and logging.
354
355
**Usage Examples:**
356
357
```typescript
358
// Comprehensive error logging
359
const { data, error } = await client
360
.from("complex_table")
361
.select(`
362
id,
363
name,
364
related_table (
365
id,
366
value
367
)
368
`)
369
.eq("status", "active");
370
371
if (error) {
372
// Log complete error context
373
console.error("Query failed:", {
374
message: error.message,
375
details: error.details,
376
hint: error.hint,
377
code: error.code,
378
httpStatus: error.status || 'unknown',
379
timestamp: new Date().toISOString(),
380
query: "complex_table with relations",
381
filters: { status: "active" }
382
});
383
384
// Extract actionable information
385
if (error.hint) {
386
console.log("Suggested fix:", error.hint);
387
}
388
389
if (error.details) {
390
console.log("Technical details:", error.details);
391
}
392
}
393
394
// Custom error handling function
395
function handlePostgrestError(error: PostgrestError, context: string) {
396
const errorInfo = {
397
context,
398
timestamp: new Date().toISOString(),
399
error: {
400
name: error.name,
401
message: error.message,
402
code: error.code,
403
details: error.details,
404
hint: error.hint
405
}
406
};
407
408
// Log to external service
409
console.error("PostgREST Error:", JSON.stringify(errorInfo, null, 2));
410
411
// Return user-friendly message
412
if (error.code === "23505") {
413
return "This record already exists. Please use different values.";
414
} else if (error.code.startsWith("23")) {
415
return "Data validation failed. Please check your input.";
416
} else if (error.code.startsWith("42")) {
417
return "There was a problem with the request. Please try again.";
418
} else {
419
return "An unexpected error occurred. Please try again later.";
420
}
421
}
422
423
// Usage with custom handler
424
const { data, error } = await client
425
.from("users")
426
.insert({ email: "test@example.com" })
427
.select();
428
429
if (error) {
430
const userMessage = handlePostgrestError(error, "user_creation");
431
// Show userMessage to user
432
console.log("User message:", userMessage);
433
}
434
```
435
436
### Type-Safe Error Handling
437
438
Leverage TypeScript for better error handling patterns.
439
440
**Usage Examples:**
441
442
```typescript
443
// Type guard for PostgrestError
444
function isPostgrestError(error: any): error is PostgrestError {
445
return error instanceof PostgrestError;
446
}
447
448
// Generic error handler with type safety
449
async function safeQuery<T>(
450
queryFn: () => Promise<PostgrestResponse<T>>
451
): Promise<{ data: T[] | null; error: string | null }> {
452
try {
453
const response = await queryFn();
454
455
if (response.error) {
456
if (isPostgrestError(response.error)) {
457
return {
458
data: null,
459
error: `Database error: ${response.error.message}`
460
};
461
} else {
462
return {
463
data: null,
464
error: "An unexpected error occurred"
465
};
466
}
467
}
468
469
return {
470
data: response.data,
471
error: null
472
};
473
} catch (error) {
474
if (isPostgrestError(error)) {
475
return {
476
data: null,
477
error: `Query failed: ${error.message}`
478
};
479
} else {
480
return {
481
data: null,
482
error: "Network or system error occurred"
483
};
484
}
485
}
486
}
487
488
// Usage with type safety
489
const result = await safeQuery(() =>
490
client.from("users").select("*").eq("active", true)
491
);
492
493
if (result.error) {
494
console.log("Error:", result.error);
495
} else {
496
// result.data is type-safe User[] | null
497
console.log("Users:", result.data?.length);
498
}
499
```
500
501
### Error Recovery Strategies
502
503
Implement strategies for recovering from different types of errors.
504
505
**Usage Examples:**
506
507
```typescript
508
// Fallback query on error
509
async function getUserWithFallback(userId: number) {
510
// Try primary query
511
const { data, error } = await client
512
.from("users_view")
513
.select("*")
514
.eq("id", userId)
515
.single();
516
517
if (!error) {
518
return { data, error: null };
519
}
520
521
// If view fails, try base table
522
if (error.code === "42P01") { // undefined_table
523
console.log("View not available, trying base table");
524
return await client
525
.from("users")
526
.select("*")
527
.eq("id", userId)
528
.single();
529
}
530
531
return { data: null, error };
532
}
533
534
// Progressive degradation
535
async function getDataWithDegradation() {
536
// Try full featured query first
537
let { data, error } = await client
538
.from("enhanced_users")
539
.select(`
540
*,
541
profiles (*),
542
settings (*)
543
`);
544
545
if (!error) {
546
return { data, level: "full" };
547
}
548
549
// Fallback to basic query
550
console.log("Enhanced query failed, trying basic query");
551
({ data, error } = await client
552
.from("users")
553
.select("id, name, email"));
554
555
if (!error) {
556
return { data, level: "basic" };
557
}
558
559
return { data: null, level: "none", error };
560
}
561
562
// Automatic retry with exponential backoff
563
async function queryWithExponentialBackoff<T>(
564
queryFn: () => Promise<PostgrestResponse<T>>,
565
maxRetries = 3
566
): Promise<PostgrestResponse<T>> {
567
let lastError: PostgrestError | null = null;
568
569
for (let attempt = 0; attempt < maxRetries; attempt++) {
570
const response = await queryFn();
571
572
if (!response.error) {
573
return response;
574
}
575
576
lastError = response.error;
577
578
// Only retry on network errors, not database constraint errors
579
if (response.error.code && !response.error.code.startsWith('08')) {
580
// Don't retry database logic errors
581
break;
582
}
583
584
if (attempt < maxRetries - 1) {
585
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
586
console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
587
await new Promise(resolve => setTimeout(resolve, delay));
588
}
589
}
590
591
return { data: null, error: lastError, count: null, status: 0, statusText: '' };
592
}
593
```