Isomorphic PostgREST client for PostgreSQL database interactions with fluent API
—
Structured error handling with PostgreSQL error details, custom error types, and response validation patterns.
Custom error class that provides detailed information about PostgREST and PostgreSQL errors.
/**
* Error format
* {@link https://postgrest.org/en/stable/api.html?highlight=options#errors-and-http-status-codes}
*/
class PostgrestError extends Error {
name: 'PostgrestError';
details: string;
hint: string;
code: string;
constructor(context: {
message: string;
details: string;
hint: string;
code: string;
});
}Usage Examples:
import { PostgrestClient, PostgrestError } from "@supabase/postgrest-js";
const client = new PostgrestClient("https://api.example.com");
// Handle PostgrestError specifically
const { data, error } = await client
.from("users")
.insert({ email: "duplicate@example.com" })
.select();
if (error) {
console.log("Error name:", error.name); // "PostgrestError"
console.log("Error message:", error.message); // Human-readable message
console.log("Error details:", error.details); // Technical details
console.log("Error hint:", error.hint); // Suggestion for fixing
console.log("Error code:", error.code); // PostgreSQL error code
// Handle specific error types
if (error.code === "23505") {
console.log("Duplicate key violation - record already exists");
}
}
// Error in try-catch block
try {
const { data } = await client
.from("restricted_table")
.select("*")
.throwOnError(); // Throws instead of returning error
} catch (error) {
if (error instanceof PostgrestError) {
console.log("PostgreSQL Error:", error.code, error.message);
console.log("Details:", error.details);
console.log("Hint:", error.hint);
} else {
console.log("Other error:", error);
}
}Different response types for success and failure scenarios.
/**
* Response format
* {@link https://github.com/supabase/supabase-js/issues/32}
*/
interface PostgrestResponseBase {
status: number;
statusText: string;
}
interface PostgrestResponseSuccess<T> extends PostgrestResponseBase {
error: null;
data: T;
count: number | null;
}
interface PostgrestResponseFailure extends PostgrestResponseBase {
error: PostgrestError;
data: null;
count: null;
}
type PostgrestSingleResponse<T> = PostgrestResponseSuccess<T> | PostgrestResponseFailure;
type PostgrestMaybeSingleResponse<T> = PostgrestSingleResponse<T | null>;
type PostgrestResponse<T> = PostgrestSingleResponse<T[]>;Usage Examples:
// Standard response handling
const response = await client
.from("users")
.select("*")
.eq("id", 123);
if (response.error) {
// Handle error case
console.log("Request failed:", response.error.message);
console.log("HTTP Status:", response.status, response.statusText);
// response.data is null, response.count is null
} else {
// Handle success case
console.log("Request succeeded:", response.data);
console.log("Record count:", response.count);
console.log("HTTP Status:", response.status, response.statusText);
// response.error is null
}
// Type-safe response handling
function handleUserResponse(response: PostgrestResponse<User>) {
if (response.error) {
// TypeScript knows this is PostgrestResponseFailure
console.error("Error code:", response.error.code);
return;
}
// TypeScript knows this is PostgrestResponseSuccess<User[]>
console.log("Users found:", response.data.length);
response.data.forEach(user => {
console.log("User:", user.name); // Fully typed
});
}
// Single response handling
const singleResponse = await client
.from("users")
.select("*")
.eq("id", 123)
.single();
if (singleResponse.error) {
console.log("User not found or multiple users found");
} else {
// singleResponse.data is User, not User[]
console.log("Found user:", singleResponse.data.name);
}Control whether errors are thrown or returned in the response.
/**
* If there's an error with the query, throwOnError will reject the promise by
* throwing the error instead of returning it as part of a successful response.
* {@link https://github.com/supabase/supabase-js/issues/92}
*/
throwOnError(): this & PostgrestBuilder<ClientOptions, Result, true>;Usage Examples:
// Default behavior - errors returned in response
const { data, error } = await client
.from("users")
.select("*")
.eq("id", 999);
if (error) {
// Handle error manually
console.log("Error occurred:", error.message);
}
// Throw on error - errors thrown as exceptions
try {
const { data } = await client
.from("users")
.select("*")
.eq("id", 999)
.throwOnError(); // Will throw if error occurs
// No need to check for error - if we reach here, it succeeded
console.log("Success:", data);
} catch (error) {
// Handle thrown error
if (error instanceof PostgrestError) {
console.log("Database error:", error.message);
} else {
console.log("Network or other error:", error);
}
}
// Useful for promise chains
client
.from("users")
.insert({ name: "John Doe", email: "john@example.com" })
.select()
.throwOnError()
.then(({ data }) => {
// Only executes on success
console.log("Created user:", data);
return data;
})
.catch((error) => {
// Only executes on error
console.error("Failed to create user:", error.message);
});Handle common PostgreSQL and PostgREST error scenarios.
Usage Examples:
// Database constraint violations
const { data, error } = await client
.from("users")
.insert({ email: "existing@example.com" });
if (error) {
switch (error.code) {
case "23505": // unique_violation
console.log("Email already exists");
break;
case "23503": // foreign_key_violation
console.log("Referenced record doesn't exist");
break;
case "23514": // check_violation
console.log("Data doesn't meet constraint requirements");
break;
case "23502": // not_null_violation
console.log("Required field is missing");
break;
default:
console.log("Database error:", error.code, error.message);
}
}
// Permission and access errors
const { data: restrictedData, error: accessError } = await client
.from("admin_table")
.select("*");
if (accessError) {
switch (accessError.code) {
case "42501": // insufficient_privilege
console.log("Access denied - insufficient permissions");
break;
case "42P01": // undefined_table
console.log("Table does not exist or is not accessible");
break;
case "42703": // undefined_column
console.log("Column does not exist");
break;
default:
console.log("Access error:", accessError.message);
}
}
// Query syntax and validation errors
const { data: queryData, error: queryError } = await client
.from("users")
.select("invalid_syntax((()))");
if (queryError) {
if (queryError.code.startsWith("42")) {
console.log("SQL syntax or schema error:", queryError.message);
console.log("Hint:", queryError.hint);
}
}Handle network-level and connection errors.
Usage Examples:
// Network timeout handling
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000); // 5 second timeout
try {
const { data } = await client
.from("large_table")
.select("*")
.abortSignal(controller.signal)
.throwOnError();
} catch (error) {
if (error.name === 'AbortError') {
console.log("Request timed out");
} else if (error instanceof PostgrestError) {
console.log("Database error:", error.message);
} else {
console.log("Network error:", error.message);
}
}
// Connection error handling
const { data, error } = await client
.from("users")
.select("*");
if (error) {
// Check for network/connection errors vs database errors
if (error.code === '' && error.message.includes('fetch')) {
console.log("Network connection error");
// Maybe show retry option to user
} else if (error.code) {
console.log("Database error:", error.code, error.message);
} else {
console.log("Unknown error:", error.message);
}
}
// Retry logic for network errors
async function queryWithRetry(maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const { data, error } = await client
.from("users")
.select("*");
if (!error) {
return { data, error: null };
}
// Retry only on network errors, not database errors
if (error.code === '' && attempt < maxRetries - 1) {
console.log(`Attempt ${attempt + 1} failed, retrying...`);
await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
continue;
}
return { data: null, error };
}
}Extract detailed information for debugging and logging.
Usage Examples:
// Comprehensive error logging
const { data, error } = await client
.from("complex_table")
.select(`
id,
name,
related_table (
id,
value
)
`)
.eq("status", "active");
if (error) {
// Log complete error context
console.error("Query failed:", {
message: error.message,
details: error.details,
hint: error.hint,
code: error.code,
httpStatus: error.status || 'unknown',
timestamp: new Date().toISOString(),
query: "complex_table with relations",
filters: { status: "active" }
});
// Extract actionable information
if (error.hint) {
console.log("Suggested fix:", error.hint);
}
if (error.details) {
console.log("Technical details:", error.details);
}
}
// Custom error handling function
function handlePostgrestError(error: PostgrestError, context: string) {
const errorInfo = {
context,
timestamp: new Date().toISOString(),
error: {
name: error.name,
message: error.message,
code: error.code,
details: error.details,
hint: error.hint
}
};
// Log to external service
console.error("PostgREST Error:", JSON.stringify(errorInfo, null, 2));
// Return user-friendly message
if (error.code === "23505") {
return "This record already exists. Please use different values.";
} else if (error.code.startsWith("23")) {
return "Data validation failed. Please check your input.";
} else if (error.code.startsWith("42")) {
return "There was a problem with the request. Please try again.";
} else {
return "An unexpected error occurred. Please try again later.";
}
}
// Usage with custom handler
const { data, error } = await client
.from("users")
.insert({ email: "test@example.com" })
.select();
if (error) {
const userMessage = handlePostgrestError(error, "user_creation");
// Show userMessage to user
console.log("User message:", userMessage);
}Leverage TypeScript for better error handling patterns.
Usage Examples:
// Type guard for PostgrestError
function isPostgrestError(error: any): error is PostgrestError {
return error instanceof PostgrestError;
}
// Generic error handler with type safety
async function safeQuery<T>(
queryFn: () => Promise<PostgrestResponse<T>>
): Promise<{ data: T[] | null; error: string | null }> {
try {
const response = await queryFn();
if (response.error) {
if (isPostgrestError(response.error)) {
return {
data: null,
error: `Database error: ${response.error.message}`
};
} else {
return {
data: null,
error: "An unexpected error occurred"
};
}
}
return {
data: response.data,
error: null
};
} catch (error) {
if (isPostgrestError(error)) {
return {
data: null,
error: `Query failed: ${error.message}`
};
} else {
return {
data: null,
error: "Network or system error occurred"
};
}
}
}
// Usage with type safety
const result = await safeQuery(() =>
client.from("users").select("*").eq("active", true)
);
if (result.error) {
console.log("Error:", result.error);
} else {
// result.data is type-safe User[] | null
console.log("Users:", result.data?.length);
}Implement strategies for recovering from different types of errors.
Usage Examples:
// Fallback query on error
async function getUserWithFallback(userId: number) {
// Try primary query
const { data, error } = await client
.from("users_view")
.select("*")
.eq("id", userId)
.single();
if (!error) {
return { data, error: null };
}
// If view fails, try base table
if (error.code === "42P01") { // undefined_table
console.log("View not available, trying base table");
return await client
.from("users")
.select("*")
.eq("id", userId)
.single();
}
return { data: null, error };
}
// Progressive degradation
async function getDataWithDegradation() {
// Try full featured query first
let { data, error } = await client
.from("enhanced_users")
.select(`
*,
profiles (*),
settings (*)
`);
if (!error) {
return { data, level: "full" };
}
// Fallback to basic query
console.log("Enhanced query failed, trying basic query");
({ data, error } = await client
.from("users")
.select("id, name, email"));
if (!error) {
return { data, level: "basic" };
}
return { data: null, level: "none", error };
}
// Automatic retry with exponential backoff
async function queryWithExponentialBackoff<T>(
queryFn: () => Promise<PostgrestResponse<T>>,
maxRetries = 3
): Promise<PostgrestResponse<T>> {
let lastError: PostgrestError | null = null;
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await queryFn();
if (!response.error) {
return response;
}
lastError = response.error;
// Only retry on network errors, not database constraint errors
if (response.error.code && !response.error.code.startsWith('08')) {
// Don't retry database logic errors
break;
}
if (attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
return { data: null, error: lastError, count: null, status: 0, statusText: '' };
}Install with Tessl CLI
npx tessl i tessl/npm-supabase--postgrest-js