Published on
2 min read

GraphQL ruby error handling

Authors

In GraphQL, we can expose errors as part of our response. Clients should check the errors field in the result. For instance, querying a non-existent field returns a system error:

{
  "errors": [
    {
      "message": "Field 'user' doesn't exist on type 'Article'",
      "locations": [{ "line": 5, "column": 5 }]
    }
  ]
}

User-Friendly Errors

For validation failures, we often want to expose errors directly on our types:

# app/graphql/types/article_type.rb
ArticleType = GraphQL::ObjectType.define do
  name "Article"
  field :title, types.String
  field :errors, types[types.String] do
    resolve -> (obj, args, ctx) { obj.errors.full_messages }
  end
end

Clients can then check these errors after a mutation:

mutation {
  createArticle(article: { title: "" }) {
    id
    errors
  }
}

Raising Execution Errors

If a resolver needs to report an error that should go into the top-level errors array, return a GraphQL::ExecutionError:

# app/graphql/mutations/article_mutations.rb
resolve -> (obj, args, ctx) {
  begin
    Article.create!(args["article"].to_h)
  rescue ActiveRecord::RecordInvalid => err
    GraphQL::ExecutionError.new("Invalid input: #{err.record.errors.full_messages.join(', ')}")
  end
}

DRY Error Handling with Resolvers

Instead of begin...rescue in every field, you can wrap resolvers:

# app/graphql/resolvers/rescue_from.rb
class RescueFrom
  def initialize(error_superclass, resolve_func)
    @error_superclass = error_superclass
    @resolve_func = resolve_func
  end

  def call(obj, args, ctx)
    @resolve_func.call(obj, args, ctx)
  rescue @error_superclass => err
    GraphQL::ExecutionError.new(err.message)
  end
end

Apply it to your fields:

# app/graphql/mutations/article_mutations.rb
field :create_article, ArticleType do
  resolve RescueFrom.new(ActiveRecord::RecordInvalid, -> (obj, args, ctx) { ... })
end

Reference: GraphQL Ruby Documentation

TwitterLinkedInHacker News