Labs ICT
Pro Login

Error Handling in Resolvers

Error Handling in Resolvers

Proper error handling is crucial for a good GraphQL API experience. GraphQL has a unique approach to errors where the response can contain both data and errors.

When a resolver throws an error, GraphQL catches it and adds it to the errors array in the response.

Basic Error Handling

When a resolver throws an error, GraphQL returns it in the response with a 200 status code:

const resolvers = {
  Query: {
    user: (parent, args, context) => {
      const user = context.db.users.find(u => u.id === args.id);
      if (!user) {
        throw new Error(`User with id ${args.id} not found`);
      }
      return user;
    },
  },
};

// Query for non-existent user
query {
  user(id: "999") {
    name
  }
}

// Response
{
  "data": {
    "user": null
  },
  "errors": [
    {
      "message": "User with id 999 not found",
      "locations": [{ "line": 2, "column": 3 }],
      "path": ["user"]
    }
  ]
}

The data is null because the resolver failed, but the error message tells you exactly what went wrong.

Error Codes

You can add custom error codes using extensions to make errors more structured:

class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

const resolvers = {
  Mutation: {
    createUser: (parent, args) => {
      if (!args.input.name) {
        throw new ValidationError('Name is required', 'name');
      }
      if (!args.input.email.includes('@')) {
        throw new ValidationError('Invalid email format', 'email');
      }
      return context.db.users.create(args.input);
    },
  },
};

// Error response with extensions
{
  "errors": [
    {
      "message": "Invalid email format",
      "path": ["createUser"],
      "extensions": {
        "code": "VALIDATION_ERROR",
        "field": "email"
      }
    }
  ]
}

Extensions let you add structured data to errors that clients can use for better error handling.

Partial Data with Errors

GraphQL can return partial data even when some fields fail. This is called error isolation:

const resolvers = {
  Query: {
    user: (parent, args, context) => {
      const user = context.db.users.find(u => u.id === args.id);
      if (!user) throw new Error('User not found');
      return user;
    },
    posts: (parent, args, context) => {
      // This resolver succeeds
      return context.db.posts.all();
    },
  },
};

// Query
query {
  user(id: "999") {
    name
  }
  posts {
    title
  }
}

// Response with partial data
{
  "data": {
    "user": null,
    "posts": [
      { "title": "First Post" },
      { "title": "Second Post" }
    ]
  },
  "errors": [
    {
      "message": "User not found",
      "path": ["user"]
    }
  ]
}

The posts field returns data even though the user field failed. This is a key difference from REST where a failed endpoint returns nothing.

Error Handling Best Practices

Use specific error messages: Tell users exactly what went wrong.

Add error codes: Make errors machine-readable for clients.

Don't expose internal details: Hide database errors and stack traces from clients.

Handle authentication errors: Return clear messages when users aren't authorized.

// Good error handling
class AuthenticationError extends Error {
  constructor(message = 'Not authenticated') {
    super(message);
    this.extensions = { code: 'UNAUTHENTICATED' };
  }
}

class ForbiddenError extends Error {
  constructor(message = 'Not authorized') {
    super(message);
    this.extensions = { code: 'FORBIDDEN' };
  }
}