Wednesday, 25 July, 2018 UTC


Summary

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.
The post Understanding the Apollo Default Resolver appeared first on Atomic Spin.

Related Stories

  • IdentityServer Authentication with an MVC Client & Unauthorized Loops
  • Migrating an Ejected Create React App to TypeScript
  • A JavaScript Object that Dynamically Returns Unknown Properties