GraphQL error handling with graphql-ruby
Tags: ruby, rails, graphql, graphqlruby
Published: 2019-05-05 21:45:23

---
title: GraphQL error handling with graphql-ruby
published: true
description: how to do error handling with graphql-ruby.
tags: Ruby, Ruby on Rails, GraphQL, graphql-ruby
---

If you want to develop a stable, high availablility application, error-handling is something you have to pay great attention to.
GraphQL is a fairly new protocol compared to RESTful and it should be dealt with completely different approaches given its unique way to communicate with clients. 
As I started developing some application with Ruby on Rails and [graphql-ruby](https://github.com/rmosolgo/graphql-ruby/), I realized that you don't see many articles on this topic yet, so I decided to share the way I do error-handling with GraphQL and Ruby on Rails.

First I'll quickly explain GraphQL's specification and practices, then go on to the main topic which is how I implement error-handling with graphql-ruby.


# table of contents
- GraphQL's specification and practices
- The variety of errors you get with GraphQL
- The implementation with graphql-ruby
- safety net with rescue_from method
- handling multiple errors with add_error method


# GraphQL's specification and practices
## practices on response's format in GraphQL
One of the GraphQL's practices is that every response is returned with http status 200 and occurred errors are included `errors` key in the response body.
That is because you can include multiple queries in a request in GraphQL.

> Since GraphQL allows for multiple operations to be sent in the same request, it's well possible that a request only partially fails and returns actual data and errors.

https://www.graph.cool/docs/faq/api-eep0ugh1wa/#how-does-error-handling-work-with-graphcool

This is a practice not specification but related tools like Apollo and graphql-ruby follow this practice and that makes us follow it too.

## GraphQL's specification about response's format
Then how do you express errors in GraphQL?
Let's take a look at the GraphQL's specification here.

https://facebook.github.io/graphql/June2018/#sec-Errors

According to the GraphQL's specification, response's format is Hash with keys named `data` and `errors`.
`data` is the key that has the actual data and `errors` is the key that has errors occurred during the execution.

```json
{
  "errors": [
    {
      "message": "hogehoge",
      "extensions": {
        "bar": "bar"
      }
    }
  ],
  "data": {
    "user": {
      "name": "Jon"
    }
  }
}
```

By the way, the format of a hash inside `errors` is specified in specification as it has 3 keys named `message`, `location` and `path`.
If you wanna have it have another key, create a key named `extensions` and put your key in it like shown below.

```json
{
  "errors": [
    {
      "message": "Name for character with ID 1002 could not be fetched.",
      "locations": [ { "line": 6, "column": 7 } ],
      "path": [ "hero", "heroFriends", 1, "name" ],
      "extensions": {
        "code": "CAN_NOT_FETCH_BY_ID",
        "timestamp": "Fri Feb 9 14:33:09 UTC 2018"
      }
    }
  ]
}
```

You want to add your own key this way because it might potentially conflicts with keys added in the future if you create your key at the same level as other default keys like `message` and `line`.

> GraphQL services should not provide any additional entries to the error format since they could conflict with additional entries that may be added in future versions of this specification.

https://facebook.github.io/graphql/June2018/#sec-Errors

# The variety of errors you get with GraphQL
Alright, now we know how to express errors in GraphQL.
Next, let's see what kind of errors you can get with GraphQL.
I'm referencing the Apollo's article on error-handling. 

[Full Stack Error Handling with GraphQL + Apollo 🚀 – Apollo GraphQL](https://blog.apollographql.com/full-stack-error-handling-with-graphql-apollo-5c12da407210)

Those are the 2 perspectives we use to categorize errors.

- Is it the client or server that is at fault?
- Where did the error occur?

We can categorize client errors into 3 types.

- parse error
  - query syntax error
- validation error
  - error at the type check phase
- execution error
  - authentication error

Server-side errors are the errors occurred in Rails API codes.

Now that we found out what kind of errors are out there, let's see how we can express them in responses.
We want to unify the format of all the responses as specified in the specification so that client tools like Apollo can parse them easily.
At this time, we will put detailed error messages into `message` key and put a key named `code` into `extensions` key which represents a status code.
This is actually the same format as that of the example response in the specification.

If you get an error at authentication phase, the response will look like this.

```json
"errors": [
    {
      "message": "permission denied",
      "locations": [],
      "extensions": {
        "code": "AUTHENTICATION_ERROR"
      }
    }
  ]
```

If an error occurred in Rails API codes, the response will look like this.

```json
"errors": [
    {
      "message": "undefined method 'hoge' for nil",
      "locations": [],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR"
      }
    }
  ]
```


# The implementation with graphql-ruby
Now that you know how to express errors in responses, let's see how you can implement those with graphql-ruby.

## how do you catch and return errors with graphql-ruby?
You can put an error into `errors` key in the response by raising `GraphQL::ExecutionError` like this.

```ruby
def resolve(name:)
  user = User.new(name: name)
  if user.save
    { user: user }
  else
    raise GraphQL::ExecutionError, user.errors.full_messages.join(", ")
  end
end
```

## Authentication error
Let's take a look at this example showing how to deal with an authentication error.

This example is based on the premise that you can get your login session by calling `current_user` method like this.

```ruby
class GraphqlController < ApplicationController
  def execute
    variables = ensure_hash(params[:variables])
    query = params[:query]
    operation_name = params[:operationName]
    context = { current_user: current_user }
    result = SampleSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
    render json: result
    #...
  end
  #...
end
``` 

Then you raise `GraphQL::ExecutionError` with the error code `AUTHENTICATION_ERROR` depends on the value of `context[:current_user]` in the `resolve` method.

```ruby
def resolve(name:, sex:)
  raise GraphQL::ExecutionError.new('permission denied', extensions: { code: 'AUTHENTICATION_ERROR' }) unless context[:current_user]

  #...
end
```

With this implementation, you get a response like this when an authentication error occurred.

```json
"errors": [
    {
      "message": "permission denied",
      "locations": [
        {
          "line": 3,
          "column": 3
        }
      ],
      "path": [
        "createUser"
      ],
      "extensions": {
        "code": "AUTHENTICATION_ERROR"
      }
    }
  ]
```

# safety net with rescue_from method
As you may know by now, it is important to unify the format of responses so that the clients can parse them easily.
If an occurred error is not rescued, general 500 internal server error is returned to the clients which forces them to be prepared to get 2 types of responses.
Obviously you do not want that to happen because you want the clients to be comfortable with handling responses.
With graphql-ruby, you can make sure responses are in the same format with `rescue_from` method.

```ruby
class SampleSchema < GraphQL::Schema
  rescue_from(StandardError) do |message|
    GraphQL::ExecutionError.new(message, extensions: {code: 'INTERNAL_SERVER_ERROR'})
  end

#...
end
```

The response will look like this.

```json
"errors": [
    {
      "message": "hogehoge",
      "extensions":  {
        "code": "INTERNAL_SERVER_ERROR"
      }
    }
  ]
```

By the way, I wrote the patch to pass Error class objects to the method :) 

[extend GraphQL::Schema::RescueMiddleware#attempt_rescue by masakazutakewaka · Pull Request #2140 · rmosolgo/graphql-ruby · GitHub](https://github.com/rmosolgo/graphql-ruby/pull/2140)

Well it's not included in a stable blanch yet so technically you can only pass a String object to the method at the moment though...

Keep in mind that this `rescue_from` method is unavailable from version 1.9.
That is because `GraphQL::Schema::RescueMiddleware` class is not supported in version 1.9 which is where `rescue_from` method is defined.
[GraphQL - Interpreter](https://graphql-ruby.org/queries/interpreter.html#compatibility)

You don't have a substitute for the method in version 1.9 and we don't even know what the additional feature to replace it might look like at the moment.
[GraphQL::Execution::Interpreter and rescue_from compatibility · Issue #2139 · rmosolgo/graphql-ruby · GitHub](https://github.com/rmosolgo/graphql-ruby/issues/2139)


# handling multiple errors with add_error method
You want to bundle up all the occurred errors in a response in some cases.
One of the cases is when you have a mutation with multiple user inputs like a sign up interface.
With graphql-ruby, you can bundle up multiple errors with `add_error` method.

```ruby
module Mutations
  class CreateUser < GraphQL::Schema::RelayClassicMutation
    argument :name, String, required: true
    argument :sex, String, required: true

    field :user, Types::UserType, null: true

    def resolve(name:, sex:)
      user = User.new({ name: name, sex: sex })
      if user.save
        { user: user }
      else
        build_errors(user)
        return # rescue_from is invoked without this
      end
    end

    def build_errors(user)
      user.errors.map do |attr, message|
        message = user[attr] + ' ' + message
        context.add_error(GraphQL::ExecutionError.new(message, extensions: { code: 'USER_INPUT_ERROR', attribute: attr }))
      end
    end
  end
end
```

The response with multiple errors in it will look like this.

```json
"errors": [
    {
      "message": "hoge already exists",
      "extensions": {
        "code": "USER_INPUT_ERROR",
        "attribute": "name"
      }
    },
    {
      "message": "fuge already exists",
      "extensions": {
        "code": "USER_INPUT_ERROR",
        "attribute": "sex"
      }
    }
  ]
```

On a side note, there is another way to achieve this which is to define the GraphQL type for the error and put errors in `data` key.
https://github.com/rmosolgo/graphql-ruby/blob/master/guides/mutations/mutation_errors.md#errors-as-data


# conclusion
In this article, I introduced the idea on how error-handling is done in GraphQL and the implementation of it using graphql-ruby.
GraphQL's specification is put together well and not too long to read so I recommend you have a read.
Hopefully this article can be helpful for those who are grappling with error-handling in GraphQL right now.

Lastly, I'm looking for a job in Canada so any messages or contacts are more than welcome!

Email: takewakamma@gmail.com

As long as it's Canada, I can relocate :)

Thank you for reading!

# references
- [GraphQL](https://facebook.github.io/graphql/June2018/#sec-Errors)
- [API | Graphcool Docs](https://www.graph.cool/docs/faq/api-eep0ugh1wa/#how-does-error-handling-work-with-graphcool)
- [Full Stack Error Handling with GraphQL + Apollo 🚀 – Apollo GraphQL](https://blog.apollographql.com/full-stack-error-handling-with-graphql-apollo-5c12da407210)
- [graphql-ruby/overview.md at master · rmosolgo/graphql-ruby · GitHub](https://github.com/rmosolgo/graphql-ruby/blob/master/guides/errors/overview.md#errors-as-data)
- [graphql-ruby/execution_errors.md at master · rmosolgo/graphql-ruby · GitHub](https://github.com/rmosolgo/graphql-ruby/blob/master/guides/errors/execution_errors.md)
- [graphql-ruby/mutation_errors.md at master · rmosolgo/graphql-ruby · GitHub](https://github.com/rmosolgo/graphql-ruby/blob/master/guides/mutations/mutation_errors.md)