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
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
andmessage
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!!