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 the same signature as a normal resolver. Continuing with the same example:


  function nameFunc(obj, 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 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.

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.

Conversation
  • Narin says:

    Thanks for writing this article! I had the same confusion, and this really helps clarify things.

  • Chris says:

    I tried this with latest graphql.js and apollo-server and I always get a GraphQL type error like this:

    If in the query resolver I have this:
    Query {
    findStuff() {
    return { count: async () => {
    // Resolve count only
    return 123; }
    }
    }

    Float cannot represent non numeric value: [function count]

    Where “Float” is whatever the field’s type is supposed to be.

    If I rely on default resolver for the value and not a function, then it works.

    // This works, and I don’t have to manually implement the resolver for the return type.
    Query {
    findStuff() {
    return { count: 123 }
    }
    }

    • Chris says:

      Also I should mention that the count function doesn’t work regardless of whether it’s async or not.

      • Patrick Bacon Patrick Bacon says:

        Interesting. I was using an older version of Apollo Server when I tested this out. The documentation still indicates this should work, but I wonder if something has changed? I’ll have to upgrade to the latest Apollo Server and try it out.

        • Chris says:

          I found out the reason and it seems like an issue with mergeSchemas from graphql-tools.

          If the resolvers are passed to mergeSchemas, they don’t do the function calling for default resolvers.

  • Comments are closed.