Overview
Rails GraphQL provides robust error handling that follows the GraphQL specification. Errors during field processing produce partial responses with detailed error information under the errors key.
Important : Exceptions are always captured and added to errors unless you return false from a rescue_from handler.
Error Response Structure
Per the GraphQL Spec , errors produce partial responses:
{
"data" : {
"user" : null ,
"posts" : [
{ "id" : "1" , "title" : "Hello" }
]
},
"errors" : [
{
"message" : "User not found" ,
"path" : [ "user" ],
"locations" : [{ "line" : 2 , "column" : 3 }],
"extensions" : {
"exception" : "ActiveRecord::RecordNotFound"
}
}
]
}
Adding Errors
The request provides three methods for adding errors:
exception_to_error
Converts exceptions to error responses:
def exception_to_error ( exception , node , ** extra )
# Adds exception to extensions
# Gets message from exception if not provided
end
Basic Usage
With Custom Message
begin
User . find (id)
rescue ActiveRecord :: RecordNotFound => e
request. exception_to_error (e, field)
end
report_node_error
Reports errors for specific nodes:
def report_node_error ( message , node , ** extra )
# Adds location from node if not provided
end
Field Error
Argument Error
request. report_node_error (
"Invalid input" ,
field,
extensions: { code: "VALIDATION_ERROR" }
)
report_error
Simplest error reporting method:
def report_error ( message , ** extra )
# Adds current stack as path if not provided
end
report_error returns nil, making it perfect for resolver return values:def welcome
return request. report_error ( 'Not authorized' ) unless authorized?
# No need for explicit nil return
end
Simple Error
With Extensions
def create_user
request. report_error ( "Email already taken" )
end
Using rescue_from
Schemas implement ActiveSupport::Rescuable for exception handling:
# From lib/rails/graphql/schema.rb
rescue_from ( PersistedQueryNotFound ) do | error |
response = { errors: [{ message: + 'PersistedQueryNotFound' }] }
error. request . force_response (response, error)
end
Exception Context
Rescued exceptions have request context:
Arguments passed to the request
Component where the error occurred
The parsed GraphQL document
Custom Error Handlers
Validation Errors
Authentication Errors
Authorization Errors
Custom Errors
# app/graphql/app_schema.rb
rescue_from ( ActiveRecord :: RecordInvalid ) do | error |
request. report_error (
error. message ,
extensions: {
code: "VALIDATION_ERROR" ,
errors: error. record . errors . as_json
}
)
end
Skipping Error Reporting
Return false to skip automatic error handling:
rescue_from ( SilentError ) do | error |
# Log it but don't add to response
Rails . logger . warn ( "Silent error: #{ error. message } " )
false # Prevents automatic error addition
end
Console Display
Errors produce formatted output in the server console:
# app/graphql/app_schema.rb
field ( :welcome ). resolve { raise 'Something went wrong.' }
Console output:
| Loc | Field | Object | Arguments | Result
---+-----+----------------+--------+-----------+--------------------
1 | GQL | schema | :base | | GraphQL::AppSchema
2 | 1:1 | query | nil | {} | _Query
3 | 1:3 | _Query.welcome | nil | {} | ↓
Something went wrong. (RuntimeError) [resolve]
/home/app/graphql/app_schema.rb:3:in `block in <class:AppSchema>'
The table shows the execution path leading to the error, making debugging easier.
Table Columns
Location in GraphQL document or “GQL” for internal
Field name or operation type
Arguments passed to the field
Field result or ”↓” if error occurred
Error Categories
Validation Errors
field ( :create_user ). perform do | input :|
user = User . new (input)
unless user. valid?
return request. report_error (
"Validation failed" ,
extensions: { errors: user. errors . as_json }
)
end
user. save!
end
Authentication Errors
field ( :current_user ). resolve do
return request. report_error ( "Not authenticated" ) unless context[ :user ]
context[ :user ]
end
Authorization Errors
field ( :delete_post ). authorize do
unless current_user. can? ( :delete , post)
request. report_error (
"Unauthorized" ,
extensions: { code: "FORBIDDEN" }
)
false # Stops execution
end
end
Business Logic Errors
field ( :transfer_funds ). perform do | from :, to :, amount :|
if from. balance < amount
return request. report_error (
"Insufficient funds" ,
extensions: {
code: "INSUFFICIENT_FUNDS" ,
available: from. balance ,
requested: amount
}
)
end
from. withdraw (amount)
to. deposit (amount)
end
Best Practices
Use Specific Handlers Create specific rescue_from handlers for different error types: rescue_from ( ActiveRecord :: RecordNotFound ) { | e | handle_not_found (e) }
rescue_from ( ValidationError ) { | e | handle_validation (e) }
rescue_from ( AuthError ) { | e | handle_auth (e) }
Include Error Codes Add machine-readable codes to extensions: extensions: {
code: "VALIDATION_ERROR" ,
field: "email"
}
Provide Context Include relevant details in error extensions: extensions: {
attempted_action: "delete_post" ,
required_role: "admin" ,
user_role: "user"
}
Log Appropriately Log errors before reporting: rescue_from ( CriticalError ) do | error |
Rails . logger . error ( "Critical: #{ error. message } " )
request. report_error ( "An error occurred" )
end
Testing Error Handling
RSpec . describe GraphQL :: AppSchema do
it "handles validation errors" do
result = execute ( <<~GQL , user : nil )
mutation {
createUser ( input : { email : "invalid" }) {
id
}
}
GQL
expect (result[ :errors ]). to be_present
expect (result[ :errors ][ 0 ][ :extensions ][ :code ]). to eq ( "VALIDATION_ERROR" )
end
end
Request Request lifecycle and processing
Events Event system for error handling