Custom GraphQL Resolvers

Many GraphQL frameworks require you to write resolvers for each field. ElasticGraph works differently: it defines a full set of resolvers for you. Simply define your schema and index your data, and it provides the full GraphQL API.

However, the GraphQL API provided by ElasticGraph won’t meet every need. Custom resolvers allow you to augment the API provided by ElasticGraph with your own custom implementation. Here’s how to define a custom resolver.

Tip ElasticGraph provides friendly error messages. Instead of reading this guide, you can jump straight to step 3 and let the error messages guide you through the changes explained in steps 1 and 2.

Step 1: Define a Resolver Class

# in lib/roll_dice_resolver.rb
class RollDiceResolver
  def initialize(elasticgraph_graphql:, config:)
    @number_of_dice = config.fetch(:number_of_dice)
  end

  def resolve(field:, object:, args:, context:)
    @number_of_dice
      .times
      .map { rand(args.fetch("sides")) + 1 }
      .sum
  end
end

Conventionally, resolvers are defined in lib (and you’d put lib on the Ruby $LOAD_PATH). As shown here, the resolver needs to define two methods:

initialize
Defines constructor logic. Accepts two arguments:
  • elasticgraph_graphql: the ElasticGraph::GraphQL instance, providing access to dependencies.
  • config: parameterized configuration values for your resolver.
resolve
Defines the resolver logic. Accepts four arguments:
  • field: the ElasticGraph::GraphQL::Schema::Field object representing the field being resolved.
  • object: the value returned by the resolver of the parent field.
  • args: arguments passed in the query.
  • context: a hash-like object provided by the GraphQL gem that is scoped to the execution of a single query.

Note There’s a fifth optional argument: lookahead. It is a GraphQL::Execution::Lookahead object which allows you to inspect the child field selections. However, providing it imposes some measurable overhead, and query resolution will be more performant if you omit it from your resolve definition.

In this case, our RollDiceResolver simulates the rolling of the configured number_of_dice, each of which has a number of sides provided as a query argument.

Step 2: Register the Resolver

  require(require_path = "roll_dice_resolver")
  schema.register_graphql_resolver :roll_dice,
    RollDiceResolver,
    defined_at: require_path,
    number_of_dice: 2

Custom resolvers must be registered with ElasticGraph in the schema definition, using the register_graphql_resolver API. Any arguments provided after defined_at: get recorded as resolver config, which will later be passed to the resolver’s initialize method. In this case, we’ve registered the resolver to roll two dice.

Step 3: Assign the Resolver to a Field

  schema.on_root_query_type do |t|
    t.field "rollDice", "Int" do |f|
      f.argument "sides", "Int" do |a|
        a.default 6
      end
      f.resolver = :roll_dice
    end
  end

Here we’ve defined a field on Query using on_root_query_type. We’ve assigned the :roll_dice resolver to our custom field.

Step 4: Query the Custom Field

That’s all there is to it! With this custom resolver wired up, we can query the custom field:

query RollDice {
  # Returns a random number between 1 and 12
  roll6SidedDice: rollDice

  # Returns a random number between 1 and 20
  roll10SidedDice: rollDice(sides: 10)
}