Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
04 Aug 2025 · 6 min read ·Article 35 / 125
Go

35 Handling Database Errors in Resolvers

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

35 Handling Database Errors in Resolvers

Error handling is a fundamental aspect of building back-end applications, especially at the resolver layer in GraphQL or REST APIs. A resolver acts as the bridge between user requests and the data processed in the database. Unfortunately, interacting with a database is highly prone to all kinds of errors—from SQL syntax errors, connection timeouts, to data constraint violations. In this article, I’ll share 35 ways to handle database errors in resolvers, complete with code examples, simulations, and flow diagrams so that the product we build becomes increasingly robust.


Why Is Database Error Handling Important?

Failing to handle database errors can have serious consequences:

  • Data corruption: if a transaction isn’t rolled back.
  • Service downtime: for example, when a failed database connection isn’t managed properly.
  • Poor user experience: error messages that aren’t informative, or worse, that leak internal details.
  • Security leaks: a stray stacktrace ending up all the way in the frontend.

Types of Errors That Arise in Databases

The following table illustrates several types of database errors along with their examples:

Error CategoryExample Error Code/Sub-CodeExample Case
Syntax Error42601 (Postgres)Typo in an SQL query (SELECT * FRM users;)
Connection ErrorECONNREFUSED, timeoutDatabase down/service hanging
Data Constraintunique_violation (23505)Inserting a user email that already exists, with a unique email field
Deadlockdeadlock_detected (40P01)Two queries waiting on each other
Not Foundempty result setA query searching for data that has already been deleted
Authorization Errorinsufficient_privilege (42501)The DB user doesn’t have read access to a certain table

Error Handling Flow Diagram in the Resolver

Let’s look at a simple flow diagram of what happens when our query executes in a resolver and encounters an error:

MERMAID
flowchart TD
  A[Client Request] --> B{Resolver}
  B --> C{DB Query}
  C -- Success --> D[Return Data]
  C -- Error --> E{Type of Error}
  E -- Connection/Timeout --> F[Log & Return 503]
  E -- Not Found --> G[Return Null / NotFound Err]
  E -- Constraint --> H[Return 400/409 & Custom Error]
  E -- Unknown --> I[Log Detail & Return 500]
  F --> J[Client]
  G --> J
  H --> J
  I --> J

35 Patterns for Handling Database Errors in Resolvers

1. Use a Try-Catch Block

js
1async function resolver(_, args, context) {
2  try {
3    return await db.query('SELECT * FROM ...');
4  } catch (err) {
5    console.error('DB Error:', err);
6    throw new Error('Internal Server Error');
7  }
8}

2. Map Error Codes to Appropriate Messages

Create a map from DB error codes to API messages.

js
1const errorMap = {
2  '23505': 'Duplicate entry',       // Unique constraint
3  '42601': 'Syntax Error',
4};
5
6catch (err) {
7  const message = errorMap[err.code] || 'Internal Error';
8  throw new Error(message);
9}

3. Use a Custom Error Class

js
1class NotFoundError extends Error {}
2class ValidationError extends Error {}

4. Complete Yet Safe Error Logging

Log the full message on the server, but show a concise version to the client.

5. Retry on Connection Errors

js
 1async function queryWithRetry() {
 2  for (let i = 0; i < 3; i++) {
 3    try return await db.query(...);
 4    catch (err) {
 5      if (isConnectionError(err)) await sleep(100);
 6      else throw err;
 7    }
 8  }
 9  throw new Error('Cannot connect to DB');
10}

6. Roll Back the Transaction on Error

js
1try {
2  await db.begin();
3  await db.query('...');
4  await db.commit();
5} catch (e) {
6  await db.rollback();
7  throw e;
8}

7. Standardize the Error Response

Define an error format:

json
1{
2  "code": "CONFLICT",
3  "message": "Duplicate record",
4  "details": "users.email must be unique"
5}

8. Separate Internal and User Errors

Internal DB errors should never “leak” to the client.

9. Use Error Boundaries in the Service/Use Case Layer

10. Validate Input Data Before Querying

11. Graceful Fallback for Not Found

js
1if (result.rows.length === 0) return null; 

12. Return Null Along With an Error Message Per the GraphQL Spec

13. Use an Error Handling Library

Such as Boom for REST.

14. Use a Deadlock/Timeout Detector

15. Implement Exponential Backoff on Retries

16. Document the Errors That Occur

Keep a log of DB error codes and their mappings in your documentation.

17. Be Consistent With HTTP Codes

409 for duplicates, 404 when not found, 500 for internal errors.

18. Prevent SQL Injection (input validation & parameterized queries)

19. Never Show the Stacktrace to the Client

20. Store Errors in an Error Monitoring Tool

Such as Sentry or Datadog.

21. Show User-Friendly Error Messages

For example: “Email is already in use”, not “23505 unique_violation”.

22. Test the Error Path With Unit Tests

23. Make Sure the DB Connection Is Always Closed

Using pooling/auto-close.

24. Handle Query Timeouts

25. Ensure Transactions Are Atomic

26. Use the Right Isolation Level for Transactions

27. Use Localized DB Error Messages (locale messages)

To make them more user-friendly.

28. Rate Limit Repeated Query Errors

To avoid DDoS/consistently clogging the server.

29. Blacklist/Ban Abusive Error-Generating IPs

30. Handle DB Errors Across Multiple Shards/Microservices

31. Build Dedicated Error Metrics & Health Checks for the DB

32. Replay the Request if It’s Idempotent

33. Avoid Redundant Queries When the DB Is Degraded

34. Use CQRS/Read-Write Split Under High Load

35. Audit-Log All Important Errors for Investigation


Simulation (Node.js/Express + PostgreSQL)

Let’s simulate handling a unique error: a duplicate key.

js
 1const { Pool } = require('pg');
 2const db = new Pool();
 3
 4async function createUser(email) {
 5  try {
 6    await db.query('INSERT INTO users(email) VALUES($1)', [email]);
 7    return { success: true };
 8  } catch (err) {
 9    if (err.code === '23505') {
10      // Unique violation
11      throw new Error('Email is already registered');
12    }
13    // Log error detail
14    console.error('[DB]', err);
15    throw new Error('An error occurred while creating the user');
16  }
17}

Conclusion

Handling database errors in resolvers isn’t just about writing a “catch” in every function—it’s about integrating best practices at each layer, documenting errors, and tailoring the level of detail in messages to each audience (dev vs. user). With the 35 strategies above, you can strengthen your application against database errors, from small scale all the way to enterprise.

Don’t hesitate to adapt and combine the techniques above so that your error handling stays scalable and easy to debug.

Happy coding, and may errors never haunt your production!

Related Articles

💬 Comments