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.