All Articles

Error Handling in GraphQL-Ruby

Error handling is the one of use case everyone run into when they start working with GraphQL-ruby. The Rails application raises multiple exceptions. For example, ActiveRecord::RecordInvalid exception is raised when a model level validation fails or ActiveRecord::RecordNotFound exception is raised when record is not found.

How to handle these exceptions without breaking the API?

GraphQL-ruby provides a exception GraphQL::ExecutionError to handle errors and rescue and raise the exception in case you run into them. When the exception is raised the server doesn’t break with 500 Internal Server Error whereas returns a JSON adding errors collection to the JSON

Let’s take an example of query which is handling ActiveRecord::RecordNotFound

# resolvers/organizations/show
class Resolvers::Organizations::Show < Resolvers::BaseResolver
  description "Returns properties of Organization"

  argument :id, ID, required: true

  type Types::OrganizationType, null: false

  def resolve(id:)
    Organization.find_by!(id: id)
  rescue ActiveRecord::RecordNotFound => error
    raise GraphQL::ExecutionError, error.message
  end
end

If you run the query in graphiql, you’ll see the errors collection in the response with each object consisting of three keys: path, location and message

enter image description here

Problem Statement

I can see three problems with the above approach.

  • We have to rescue each exception in all the resolvers and mutations.
  • The structure of the errors with path, location and message is often difficult to process and display the errors to the end-user on front-end.
  • Multiple validation errors are even harder to process if we want to show the errors inline in the form.

Solution

GraphQL-ruby in the latest version has added GraphQL::Execution::Errors to easily rescue all the exceptions which occur runtime while resolving fields. This addition was inspired by graphql-errors gem.

Now we can easily rescue the exception and not have to repeatedly handle the exception in all resolvers and mutations.

class MySchema < GraphQL::Schema
  use GraphQL::Execution::Errors

  # rescue ActiveRecord::RecordNotFound exception
  rescue_from(ActiveRecord::RecordNotFound) do |err, obj, args, ctx, field|
    raise GraphQL::ExecutionError, err
  end
end

The second problem is processing the errors on the frontend. You can override the GraphQL::ExecutionError exception and render the errors the way you want.

# config/initializer/execution_error.rb
class GraphQL::ExecutionError < GraphQL::Error
  attr_accessor :error, :record

  def initialize(error)
    @error = error
    @record = error.try(:record)
  end

  def to_h
    if record.present?
      record.errors.messages
    else
      error
    end
  end
end

I prefer to render the Rails errors object. This way it solves both the issues. The rendering of multiple validation errors and processes the errors collection in the proper format without unnecessary path and location details. You can update the to_h method here and return the errors the way you want them.

In case of record not found i.e: ActiveRecord::RecordNotFound exception the error would look in following way.

{
  "data": null,
  "errors": [
    "Couldn't find Organization"
  ]
}

In case of validation errors i.e: ActiveRecord::RecordInvalid exception the error would look in following way. We can now easily map the error keys(eg: title) to the form elements and show the errors in inline way.

{
  "data": {
    "createOrganization": null
  },
  "errors": [
    {
      "title": [
        "can't be blank"
      ],
      "description": [
        "can't be blank"
      ]
    }
  ]
}

Happy Coding!!