Having worked with GraphQL in Ruby on Rails for a while now, I’ve run into issues with standardizing error responses from mutations across the API.
What’s an appropriate data type to use for errors? A single string? An array of strings? A complex type? The answer depends on what you’re trying to do.
In Ruby, you’d first define a BaseMutation
class that all mutation classes inherit from. Consider the following:
class BaseMutation < GraphQL::Schema::RelayClassicMutation
def authorized?(**args)
if !context[:current_user].present?
return false, { errors: ["You need to be signed in."]}
else
true
end
end
end
class SubmitDocumentMutation < BaseMutation
argument :document_id, ID, required: true
field :document, DocumentType, null: true
field :errors, DocumentErrorType, null: true
def resolve(document_id:)
d = Document.find(document_id)
if d.valid_for_submission?
{ document: d }
else
{ errors: d.full_errors }
end
end
end
type FieldThreeType {
subFieldOne: Int!
subFieldTwo: String!
}
type DocumentType {
fieldOne: String!
fieldTwo: Int!
fieldThree: FieldThreeType!
}
type FieldThreeErrorType {
subFieldOne: String!
subFieldTwo: String!
}
type DocumentErrorType {
fieldOne: String!
fieldTwo: String!
fieldThree: FieldThreeErrorType!
}
The issue here is that if the user isn’t authorized, BaseMutation
will return an array of strings for the error
field, but on the SubmitDocument
mutation, the errors
field is not defined as a string array. This would actually throw an exception within graphql-ruby and the server would respond with a 500.
One approach to dealing with these situations is to separate the errors based on concern.
class BaseMutation < GraphQL::Schema::RelayClassicMutation
#...
field :success, Boolean, null: false
field :system_errors, [String], null: true
#...
end
class SubmitDocumentMutation < BaseMutation
#...
field :document, DocumentType, null: true
field :document_errors, DocumentErrorType, null: true
#...
end
By defining fields on the BaseMutation
, all mutations will have these fields in their definition. This may seem like pollution, but it’s perfectly fine.
We’ve split the errors by using different fields, thus giving the API more flexibility in its error handling. We’ve also added a success
flag for convenience, so clients can easily determine if there are errors without having to check every possible error field (imagine a mutation that has several error fields).
An alternative approach would be to drop the success
flag in favor of error codes. This is more verbose but it gives clients more visibility when doing their own error handling.
class BaseMutation < GraphQL::Schema::RelayClassicMutation
#...
field :error_code, ErrorCodeEnumType, null: true
field :system_errors, [String], null: true
#...
end
class ErrorCodeEnumType < GraphQL::Schema::Enum
value "Signed_Out", "User is not signed in"
value "Forbidden", "User not allowed to perform this action"
#...
value "Document_Invalid", "Document is not valid for submission"
#...
end
These are only a few approaches and should serve as a starting point for changing the way you think about error handling in GraphQL.