Resolvers

A GraphQL::Schema::Resolver is a container for field signature and resolution logic. It can be attached to a field with the resolver: keyword:

# Use the resolver class to execute this field field :pending_orders, resolver: PendingOrders 

Under the hood, GraphQL::Schema::Mutation is a specialized subclass of Resolver.

First, ask yourself …

Do you really need a Resolver? Putting logic in a Resolver has some downsides:

Here are a few alternatives to consider:

field :recommended_items, [Types::Item], null: false def recommended_items ItemRecommendation.new(user: context[:viewer]).items end 
# Generate a field which returns a filtered, sorted list of items def self.items_field(name, override_options, &block) # Prepare options default_field_options = { type: [Types::Item], null: false } field_options = default_field_options.merge(override_options) # Create the field field(name, **field_options) do argument :order_by, Types::ItemOrder, required: false argument :category, Types::ItemCategory, required: false # Allow an override block to add more arguments instance_eval(&block) if block_given? end end # Then use the generator to create a field: items_field(:recommended_items) do |field| field.argument :similar_to_product_id, ID, required: false end # Implement the field def recommended_items # ... end 

As a matter of code organization, that class method could be put in a module and shared between different classes that need it.

module HasRecommendedItems def self.included(child_class) # attach the field here child_class.field(:recommended_items, [Types::Item], null: false) end # then implement the field def recommended_items # ... end end # Add the field to some objects: class Types::User < BaseObject include HasRecommendedItems # adds the field end 

When do you really need a resolver?

So, if there are other, better options, why does Resolver exist? Here are a few specific advantages:

Using resolver

Use the base resolver class:

module Resolvers class RecommendedItems < BaseResolver type [Types::Item], null: false description "Items this user might like" argument :order_by, Types::ItemOrder, required: false argument :category, Types::ItemCategory, required: false def resolve(order_by: nil, category: nil) # call your application logic here: recommendations = ItemRecommendation.new( viewer: context[:viewer], recommended_for: object, order_by: order_by, category: category, ) # return the list of items recommendations.items end end end 

And attach it to your field:

class Types::User < Types::BaseObject field :recommended_items, resolver: Resolvers::RecommendedItems end 

Since the Resolver lifecycle is managed by the GraphQL runtime, the best way to test it is to execute GraphQL queries and check the results.

Nesting resolvers of the same type

You may run into cyclical loading issues when using a resolver within the definition of the type the resolver returns e.g.

# app/graphql/types/query_type.rb module Types class QueryType < Types::BaseObject field :tasks, resolver: Resolvers::TasksResolver end end # app/graphql/types/task_type.rb module Types class TaskType < Types::BaseObject field :title, String, null: false field :tasks, resolver: Resolvers::TasksResolver end end # app/graphql/resolvers/tasks_resolver.rb module Resolvers class TasksResolver < BaseResolver type [Types::TaskType], null: false def resolve [] end end end 

The above can produce the following error: Failed to build return type for Task.tasks from nil: Unexpected type input: (NilClass).

A simple solution is to express the type as a string in the resolver:

module Resolvers class TasksResolver < BaseResolver type "[Types::TaskType]", null: false def resolve [] end end end 

In doing so, you can defer the loading of the type class until the nested resolver has already been loaded.

Extensions

In cases when you want your resolvers to add some extensions to the field they resolve, you can use extension method, which accepts extension class and options. Multiple extensions can be configured for a single resolver.

class GreetingExtension < GraphQL::Schema::FieldExtension def resolve(object:, arguments:, **rest) name = yield(object, arguments) "#{options[:greeting]}, #{name}!" end end class ResolverWithExtension < BaseResolver type String, null: false extension GreetingExtension, greeting: "Hi" def resolve "Robert" end end