Pankaj Vishwani
Pankaj Vishwani

Reputation: 342

graphql: sort by nested field

Let's say I have 2 tables:
- Users (id, name, post)
- Posts (id, message, user)

How can I fetch first 10 Posts order by User's name(desc)?

Here's how my schema looks like:

var PostType = new GraphQLObjectType({
  name: "Post",
  fields: () => ({
    id: { type: GraphQLInt },
    message: { type: GraphQLString },
    user: {
      type: UserType,
      args: {
        orderBy: { type: sortType }
      },
      resolve(parent, args) {
        console.info("Post resolve called.");
        return userMap[parent.user];
      }
    }
  })
});

var RootQuery = new GraphQLObjectType({
  name: "RootQueryType",
  fields: {
    allPosts: {
      type: new GraphQLList(PostType),
      resolve(parentValue, args) {
        console.info("allPosts resolve called.");
        return postData;
      }
    }
  }
});

And Query:

{
  allPosts {
    message
    user (orderBy: {field: "name", direction: ASC}) {
      name
    }
  }
}

Is there any way, I can call user resolver function before allPosts resolver function? Because, I am trying to fetch 10 users sorted by name and then pass post ids to allPosts resolver.

Upvotes: 14

Views: 14500

Answers (1)

Daniel Rearden
Daniel Rearden

Reputation: 84687

GraphQL fields are resolved in a top-down fashion. That means allPosts is resolved first, then then message and user fields (simultaneously) and then the name field. This has to happen, as the "parent" or root field's resolved value determine's the value that's then passed to the resolver for its children fields as the root value. Information flows from "higher" resolvers to "lower" ones, but not the other way around.

Your orderBy argument here probably should be an argument on the allPosts field rather than the user field. There's two reasons to do that: (1) conceptually, regardless of the sort criteria, you are sorting the Posts returned by allPosts -- by convention, it just makes sense to put the sort there; (2) the argument is probably needed by the allPosts resolver more than it's needed by the user resolver.

To make the above work, you'll probably need to modify how you identify the sort criteria (making field a path like user.name for example). You may also need "lift" the logic for populating the users up into the allPosts resolver. For example:

resolve(parentValue, { sortBy: { path, direction } }) {
  const data = postData.map(post => {
    post.user = userMap[post.user]
    return post
  });
  // using lodash
  return orderBy(data, [(post) => get(post, path)], [direction])
}

It is possible to determine the selection set for other fields inside the request, including the arguments, by parsing the info object that's passed in as the fourth parameter to the resolver function. It's a pain though and I don't know if this particular case really justifies doing all that. You can read more about that approach in this answer.

Upvotes: 5

Related Questions