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)
@multiplier = config.fetch(:multiplier)
end
def resolve(field:, object:, args:, context:)
@number_of_dice
.times
.map { rand(args.fetch("sides")) + 1 }
.sum * @multiplier
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
: theElasticGraph::GraphQL
instance, providing access to dependencies. -
config
: parameterized configuration values for your resolver.
-
resolve
- Defines the resolver logic. Accepts four arguments:
-
field
: theElasticGraph::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. Finally, it multiplies the dice roll by a configured multiplier
.
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: Configure a Field to use the Resolver
schema.on_root_query_type do |t|
t.field "rollDice", "Int" do |f|
f.argument "sides", "Int" do |a|
a.default 6
end
f.resolve_with :roll_dice, multiplier: 3
end
end
Here we’ve defined a field on Query
using on_root_query_type
,
and configured it to use the :roll_dice
resolver. Extra arguments (multiplier: 3
, in this case) will be passed to the resolver in config
.
Note
Resolver config values can be provided both when registering the resolver (via schema.register_graphql_resolver
)
and when configuring a field to use the resolver (via field.resolve_with
). These configuration options will be
merged together to provide config
when instantiating the resolver.
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 6 and 36 (2 dice of 6 sides x 3)
roll6SidedDice: rollDice
# Returns a random number between 6 and 60 (2 dice of 10 sides x 3)
roll10SidedDice: rollDice(sides: 10)
}