How to handle Postgres errors inside try catch (with Drizzle)? #916
Replies: 7 comments 5 replies
-
Does anyone know ? I'm stumbling upon the same situation right now. |
Beta Was this translation helpful? Give feedback.
-
I noticed that the error message comes from the database library I use in drizzle. In my case, it is "pg". |
Beta Was this translation helpful? Give feedback.
-
Is this concept possible in the first place? I'm running a try catch inside a transaction to watch for a db conflict error, but the entire transaction is throwing this error while the closure in the transaction keeps on running. For example: await db.transaction(async (tx) => {
let retryCount = 0;
while (retryCount < 10) {
try {
await tx.insert()
} catch (error) {
const queryError = error as QueryError;
if (
typeof queryError.code === "string" &&
queryError.code === "23505"
) {
// Unique violation (Postgres specific error code)
// Regenerate slug by appending a unique identifier (e.g., retryCount)
slug = slugify(knowledge.title) + "-" + (retryCount + 1);
retryCount++;
} else {
throw error; // Re-throw other errors
}
}
}
} But once the first error is thrown, the entire transaction seems to return, while the code just continues to execute. |
Beta Was this translation helpful? Give feedback.
-
You can use the errors thrown by postgres, though that does have some of its own problems. import pg from 'postgres';
const isUniqueConstraintError = (error: unknown): boolean => {
/** https://github.com/porsager/postgres/pull/901 */
// eslint-disable-next-line import/no-named-as-default-member
return error instanceof pg.PostgresError && error.code === '23505';
};
export default isUniqueConstraintError; import isUniqueConstraintError from '@/lib/database/errors/isUniqueConstraintError';
// Insert item into db
try {
// Insert item into the database with the given data and max position
await db.insert(cars).values({ ...data });
} catch (error) {
// Check if the error is a unique constraint error
if (isUniqueConstraintError(error)) {
// handle however you want....
} else {
// Handle other errors
....
}
} Ofcourse, you can use the PostgresError to get more details. |
Beta Was this translation helpful? Give feedback.
-
This is the weird way that I've worked around this. Drizzle will throw a import { DrizzleQueryError } from "drizzle-orm/errors";
import postgres from "postgres";
export const isUniqueConstraintError = (error: unknown): boolean => {
/** https://github.com/porsager/postgres/pull/901 */
// eslint-disable-next-line import/no-named-as-default-member
if (!(error instanceof DrizzleQueryError)) {
return false;
}
const cause = error.cause;
if (!cause || typeof cause !== 'object') {
return false;
}
return 'code' in cause && (cause as postgres.PostgresError).code === "23505";
}; |
Beta Was this translation helpful? Give feedback.
-
Background:Drizzle depends on database drivers to perform actual operations on the database. For Now the problem is, since drizzle does not perform the database operations itself it gets success/failure information about the operation through errors/messages returned by the underlying driver. The drizzle's session layer is driver agnostic. So the database interation code looks like this: try {
return await query();
}
catch (e) {
throw new DrizzleQueryError(queryString, params, e as Error);
} Basically, it just tries to run the query using the database driver provided, and in case, any error is thrown at a lower level, it just:
There are several problems with this approach:
Though I think this is a difficult problem, because:
I think the best approach out here, is to define a common interface for all Errors (can expand DrizzleQueryError to include more fields). And then, in the catch block above, try to find out what went wrong and populate as must information we have before we throwing the Temporary solutionI'm using Upon failure, it throws a So I build the following solution, to detect errors: import { DrizzleQueryError } from "drizzle-orm";
import { DatabaseError } from "pg-protocol";
// Relevant codes from: https://www.postgresql.org/docs/current/errcodes-appendix.html
export enum OPSTATUS {
SUCCESS,
// integrity violations
FOREIGN_KEY_VIOLATION=23503,
UNIQUE_VIOLATION=23505,
CHECK_VIOLATION=23514,
NOT_NULL_VIOLATION=23502,
// transaction failure
INVALID_TRANSACTION_STATE=25000,
// connection failure
CONNECTION_DOES_NOT_EXIST=8006,
CONNECTION_FAILURE=8006,
// other
UNKNOWN_FAILURE=-1,
}
// ...
try {
const insertedUser = await db.insert(usersSchema).values(user).returning();
if ( insertedUser[0] === undefined ) {
throw new Error("Unknown Failure"); // handled below
}
return insertedUser[0];
}
catch (err: any) {
// if error has not originated from pg driver, no useful information can be extracted
if ( !(err instanceof DrizzleQueryError) || !(err.cause instanceof DatabaseError) ) {
return {
success: false,
status: OPSTATUS.UNKNOWN_FAILURE,
message: "Unknown Failure"
}
}
// ...
// try to find out what went wrong?
switch (errCode) {
case OPSTATUS.UNIQUE_VIOLATION: {
recommendedHttpResponseCode = StatusCodes.CONFLICT;
message = "User already exists";
break;
}
case OPSTATUS.CONNECTION_FAILURE: {
recommendedHttpResponseCode = StatusCodes.SERVICE_UNAVAILABLE;
message = "Broken connection to database server";
break;
}
// and so on..
}
// return approapriate response
} |
Beta Was this translation helpful? Give feedback.
-
One of the most effective approach that worked for me was to create a utility function that extracts the original database error from Drizzle's 1. The Error Handler UtilityCreate a new file, for example, import { DrizzleQueryError, DrizzleError } from 'drizzle-orm';
import { DatabaseError } from 'pg';
// Defines the shape of the error handler functions
type ErrorHandler = (error: DatabaseError) => { message: string; constraint: string | null };
// Maps PostgreSQL error codes to specific handler functions
const PostgresErrorHandlers: Record<string, ErrorHandler> = {
'23505': (error) => ({
message: 'A duplicate entry was found for a unique field.',
constraint: error.constraint || null,
}),
'23503': (error) => ({
message: 'A foreign key violation occurred. The record you are trying to link does not exist.',
constraint: error.constraint || null,
}),
'22P02': () => ({
message: 'The data provided is in an invalid format (e.g., not a valid UUID).',
constraint: null,
}),
'23514': (error) => ({
message: 'A check constraint was violated.',
constraint: error.constraint || null,
}),
'23502': (error) => ({
message: `A required field is missing. The column '${error.column}' cannot be null.`,
constraint: error.column || null,
}),
'42703': (error) => ({
message: 'An undefined column was referenced in the query.',
constraint: error.column || null,
}),
'42601': () => ({
message: "There's a syntax error in the database query.",
constraint: null,
}),
'25000': () => ({
message: 'Transaction failed: a data integrity issue occurred within a database transaction.',
constraint: null,
}),
'08006': () => ({
message: 'Database connection failed. The database may be unavailable.',
constraint: null,
}),
'42P01': () => ({
message: 'A referenced table does not exist in the database.',
constraint: null,
}),
'40001': () => ({
message: 'Transaction serialization failure. Please retry the transaction as it could not be completed due to concurrent modifications.',
constraint: null,
}),
'default': (error) => ({
message: `A database error occurred: ${error.message}`,
constraint: null,
}),
};
/**
* Extracts a user-friendly message and constraint from a Drizzle ORM error.
* @param error The error object from Drizzle.
* @returns An object with the main error message and constraint name (if applicable).
*/
export function getDbErrorMessage(error: unknown): { message: string; constraint: string | null } {
if (error instanceof DrizzleQueryError && error.cause instanceof DatabaseError) {
const originalError = error.cause;
const handler = PostgresErrorHandlers[originalError.code ?? "default"];
if (handler) {
return handler(originalError);
}
// Default case for any other unhandled DatabaseError
return {
message: `A database error occurred: ${originalError.message}`,
constraint: null,
};
}
// Fallback for generic Drizzle errors or other Error instances
if (error instanceof DrizzleError || error instanceof Error) {
return {
message: error.message || 'An unexpected error occurred.',
constraint: null,
};
}
// Final fallback for unknown error types
return { message: 'An unknown error occurred.', constraint: null };
} 2. How to Use It in Your CodeIn your controller or service layer, wrap your database calls in a // Example usage in a service or controller
import { db } from './db/db'; // Your Drizzle instance
import { users } from './db/schema';
import { getDbErrorMessage } from './dbErrorUtils'; // The new utility
export async function createUser(data: typeof users.$inferInsert) {
try {
const result = await db.insert(users).values(data).returning();
return { success: true, user: result[0] };
} catch (error) {
const { message, constraint } = getDbErrorMessage(error);
// Log the error for debugging
console.error('Database operation failed:', { message, constraint, originalError: error });
// Throw a specific error for your API handler to catch and format
// You could also return a specific error object here
throw new Error(message);
}
} This approach provides a clean separation of concerns and a reliable way to get clear, actionable insights from your database errors, making debugging faster and your application more robust. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
I'm trying to understand how to properly type my errors when Drizzle throws due to a Postgres error.
Example, when I attempt to insert a number into a string column, I was thinking within my catch block I could use:
Should I just use PostgresError?
Thanks!
Beta Was this translation helpful? Give feedback.
All reactions