or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

client-configuration.mderror-handling.mdfiltering-conditions.mdindex.mdquery-operations.mdresult-transformation.mdstored-procedures.md

error-handling.mddocs/

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

```