Understanding the Apollo Default Resolver

July 25, 2018 0 Comments

Understanding the Apollo Default Resolver

 

 

According to documentation for Apollo’s GraphQL-tools:

You don’t need to specify resolvers for every type in your schema. If you don’t specify a resolver, GraphQL.js falls back to a default one…

The documentation goes on to state that the default resolver will look for a property on the parent object with the field name that’s being resolved. If that property is not a function, the value of the property is returned. But if the property does contain a function, then the default resolver calls it, and “passes the query arguments into that function.”

It wasn’t clear to me exactly what this meant, so I did some experimenting.

Normal Resolver Signature

The documentation states that every resolver function accepts four positional arguments:

 fieldName(obj, args, context, info) { result } 

You can read the Apollo documentation for an explanation of each argument.

When a resolver is explicitly specified for a type/field, that resolver will always be used. And even if a parent resolver provides a value for a field, it will be overridden by the result of the explicitly specified resolver.

 const resolverMap = { Query: { getAuthor(obj, args, context, info) { return { name: "Frank" }; }, }, Author: { name(obj, args, context, info) { console.log("Provided name", obj.name); return "Hank"; } }, }; 

The above resolvers would resolve to “Hank” for the author’s name (but the console.log would print out “Frank”).

Default Resolver

However, if a resolver is not specified for a field, then the default resolver is used. As described above, if the parent object has a property with the relevant field name, that value will be returned. Modifying the previous example:

 const resolverMap = { Query: { getAuthor(obj, args, context, info) { return { name: "Frank" }; }, }, }; 

This code results in the name of the author always resolving to “Frank.”

If the property with the relevant field name contains a function, the default resolver will call that function. This function has nearly the same signature as a normal resolver, minus the first obj argument. Continuing with the same example:

 function nameFunc(args, context, info) { return "Sal"; } const resolverMap = { Query: { getAuthor(obj, args, context, info) { return { name: nameFunc }; }, }, }; 

In this configuration, the author’s name will always be “Sal.”

Note that the args argument (in the first position) are any arguments specified for that particular field. So for this query:

 query { getAuthor(id: 5){ name(foo: "bar") } } 

The nameFunc above would have an args value of { foo: "bar" } passed into it.

The context and info arguments are the same as for a normal resolver.

Conclusion

I’ve definitely gotten confused by the different ways a resolver can be specified and what can be passed into a resolver function. Hopefully, this post can help clarify things a bit.


Tag cloud