Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to resolve nested types in GraphQL?

I'm having trouble resolving graphql nested types. I can successfully get the UserMetrics nested resolver to fire, but the parent resolver object (user) is null. Am I misunderstanding the GraphQL resolver map?

Schema:

type User {
    id: String!
    metrics: UserMetrics
}

type UserMetrics {
    lastLogin: String!
}

Resolver:

Query: {
    user(_, { id }, ctx) {
        return { id }; 
    }
},

User: {
    metrics(): ({}), // UserMetrics.lastLogin doesn't fire without this
},

UserMetrics: {
    lastLogin(user) {
        console.log(user); // null
    }
},
like image 311
James Mensch Avatar asked Jul 13 '17 14:07

James Mensch


People also ask

How do you make a nested query in GraphQL?

You can use the object (one-to-one) or array (one-to-many) relationships defined in your schema to make a nested query i.e. fetch data for a type along with data from a nested or related type. The name of the nested object is the same as the name of the object/array relationship configured in the console.

What is type resolver in GraphQL?

A resolver is a function that resolves a value for a type or field in a schema. Resolvers can return objects or scalars like Strings, Numbers, Booleans, etc. If an Object is returned, execution continues to the next child field. If a scalar is returned (typically at a leaf node), execution completes.

What is __ Typename in GraphQL?

The __typename field returns the object type's name as a String (e.g., Book or Author ). GraphQL clients use an object's __typename for many purposes, such as to determine which type was returned by a field that can return multiple types (i.e., a union or interface).

How do you query in GraphiQL?

The GraphiQL client allows you to create variables for use in your queries. To add a query variable, click the Query Variables pane and enter a JSON object that defines your variable. To use a variable in your query, prepend the $ character to your variable name and use it to replace the desired value.


1 Answers

This can be a little confusing to wrap your head around, and unfortunately the docs don't provide a solid example of how nested types work. According to the docs, the first argument the resolver takes is:

The object that contains the result returned from the resolver on the parent field

So, the parent (User) resolves a certain field (metrics). If this field is a scalar, it doesn't need to do anything else and that's what's returned. Otherwise the value the parent resolved is passed down to the resolvers associated with the "child" type (UserMetrics). Each of those resolvers then uses that value to resolve the fields it is responsible for.

In the code you provided, User is resolving metrics as an empty object (i.e. {}). This is the value that's passed to the resolver for any field inside UserMetrics. Strictly speaking, in this case, running console.log on that value will return {}, not null.

Try this: Comment out your resolver for the metrics field, and instead of { id }, have your query return { id, metrics: { lastLogin: 'foo' }}. Run that and you should see a console output showing the object that was passed to the resolver for lastLogin, in this case { lastLogin: 'foo' }.

Step by step, what's happening here:

  1. The user field of the query is resolving to { id: 'whateverIdYouProvided', metrics: { lastLogin: 'foo' }}. Since it's returning a User type, and not a scalar, it passes this value down to the resolvers for any fields inside User.
  2. A field like id has no resolver specified in this case, so it uses the default resolver. This just looks at the object it received, and if it finds a property that matches the name of its field, it resolves to the value of that property. If it can't find any property by that name, it returns null. In this case, id is a scalar, so it just resolves to 'whateverIdYouProvided' and doesn't need to go any further.
  3. Because we commented out the metrics resolver, this field will also use the default resolver. In this case, it resolves to a type (UserMetrics), so it looks at the object it was given, finds a property called metrics and passes that value (i.e. { lastLogin: 'foo' }) to any resolvers for fields inside UserMetrics.
  4. UserMetrics has a field called lastLogin. Its resolver receives the above value -- { lastLogin: 'foo' } -- to work with. If you don't specify a resolver here, again the default resolver will be used and it will happily find a property called lastLogin and resolve to it's value ('foo'). If you provide a custom resolver, as you have done, obviously it will resolve to whatever you return (in the case of your code, null).

If the lastLogin resolver isn't being called at all, verify that you've actually included that field in your query! Any fields not included in the query don't need to be resolved, and so their resolvers aren't called.

like image 96
Daniel Rearden Avatar answered Sep 18 '22 20:09

Daniel Rearden