Hylle
Hylle

Reputation: 1117

Wrong order of GraphQL resolver arguments (root, args, context)

I'm wondering why my arguments seem to be switched around inside my GraphQL resolver. I'm using express-graphql.

Example of one resolver:

  getLocalDrivers: async (parent, args, ctx) => {
    console.log(ctx);
  }

I've written the argument names as they appear in the docs: http://graphql.org/learn/execution/

But when I debug and inspect the objects, it seems the args object is 1st, the context is 2nd, and the parent/root is 3rd.

parent:

Object {location: "020202"}

args:

IncomingMessage {_readableState: ReadableState, readable: false, domain: null, …}

context:

Object {fieldName: "getLocalDrivers", fieldNodes: ....

Some server code:

app.use(
  "/graphql",
  graphqlHTTP({
    schema,
    graphiql: true,
    rootValue: rootResolver
  })
);

My rootResolver:

var rootResolver = {
     getLocalDrivers: async (obj, args, ctx) => {
       console.log(ctx);
  }
}

Schema:

var { buildSchema } = require("graphql");
var schema = buildSchema(`
  type Query {
    getLocalDrivers(location: String): [Driver]
  }

  type Driver {
    name: String
    location: String    
  }`);

Upvotes: 5

Views: 2821

Answers (1)

Daniel Rearden
Daniel Rearden

Reputation: 84667

If a resolve function is defined for a field, when GraphQL resolves that field, it will pass four parameters to that function:

  1. the value the parent field resolved to (commonly referred to as obj or root)
  2. the arguments for that field
  3. the context
  4. an info object that describes the entire GraphQL request

If a resolve function is absent for a particular field, GraphQL will utilize the default resolver, which simply searches for a property on the parent field and uses that if it's found.

So your getLocalDrivers query could return an array of Driver objects, and as long as the Driver object has a name property, the name field will resolve to that property's value.

Coincidentally, the name property on that Driver object could also be a function. In that case, GraphQL would call that function to get its return value. And very much like a resolver, GraphQL passes some information to this function as parameters, namely 1) the arguments, 2) the context and 3) the info object. When fields are resolved in this way, the "obj" parameter is omitted.

Ok, so what about the root?

The root object is just the object that serves as the "parent field value" that's given to queries and mutations, which are fields like everything else.

So, if you haven't defined a "resolve" function for getLocalDrivers (because you compiled you schema with buildQuery for example), GraphQL will utilize the default resolver, and use the root object you passed in as the "parent field value". It sees a getLocalDrivers, but as described above, because this a function, it calls that function with the above-mentioned three parameters.

So what's the lesson here?

Don't use root.

Seriously. Either define your schema as an object, or if you want to write the schema out using GraphQL schema language, use graphql-tools -- makeExecutableSchema makes dealing with resolvers much much easier.

const typeDefs = `
  type Query {
    getLocalDrivers(location: String): [Driver]
  }

  type Driver {
    name: String
    location: String    
  }
`
const resolvers = {
  Query: {
    getLocalDrivers: (obj, args, ctx) => {
       console.log({obj, args, ctx})
    }
  }
}
const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
})

Upvotes: 4

Related Questions