Search code examples
javascriptgraphqlexpress-graphql

Root Methods for GraphQL Sub Field Queries


I am new to GraphQL and am not sure how to implement queries on sub fields.

For example, say we have the following types and queries:

type Author {
    joinedDate: String,
    quote: String
}

type Post {
    id: String,
    author: Author,
    text: String,
    timeStamp: String,
    location: String
}

type Query {
    posts(authorId: String): [Post]
}

Clients could then make requests like:

// gets everything
{
    posts(authorId: Steve)
}
// just gets time, text, and location
{
    posts(authorId: Steve) {
        timeStamp
        text
        location
    }
}    

The root object could then implemented like so:

const root = {
    posts: (authorId) => {
        return dataService.getPosts(authorId)
    }
}

My question is how would you implement queries/filters on the sub fields. For example:

// just gets time, text, and location for specific date range
{
    posts(authorId: Steve) {
        timeStamp(minDate: 01012015, maxDate: 10102018)
        text
        location
    }
}

How would I define that root method? Would I need to filter the full list of posts manually in the posts root method?

const root = {
    // would this method even have access to the date args?
    posts: (authorId, minDate, maxDate) => {
        const newList = filterTheList(
            dataService.getPosts(authorId),
            minDate,
            maxDate
        )
        return newList
    }
}   

Thanks for reading!


Solution

  • If dataService.getPosts() doesn't provide some way for you to pass in the filters and you can't modify that code, then, yes, you would have to filter the results yourself before returning them inside the resolver.

    Otherwise, if dataService just queries a database, you would just refactor getPosts() to limit your database query to a subset based on the arguments passed to it.

    If you're trying to limit the posts being returned to a specific date range, it doesn't really make sense to have minDate and maxDate arguments for your timestamp field. Instead, they should be arguments on your posts field just like authorId.

    The only time you should add arguments to a field is when you want to change the behavior of that field's resolver. As an example, let's say youhave a field on your Post type like commenters. The list of commenters for a post isn't returned as part of the list of posts you get by calling getPosts() -- instead you need to fetch it for each post. If you wanted to filter or otherwise change the way that list of commenters is fetched or returned, then you would add one or more arguments to the commenters field.

    To address your question more directly, the arguments passed to your resolver are only the ones for that particular field -- your resolver for posts will not get the arguments for the fields under it.

    Last but not least, when writing your resolver, keep in mind what arguments are passed to it. If you are using the root value to define your resolvers, then the resolver looks like this:

    const root = {
      posts: (args, context, info) => {
        //args.authorId
      }
    }
    

    Note that you get a single object for your arguments -- they are not passed separately to the resolver.

    When you use the root value to define your resolvers, you lose the ability to define resolvers for fields of any types other than Query, Mutation and Subscription. I would encourage you to look into using graphql-tools' makeExecutableSchema. It simplifies building your schema while still offering flexibility.

    Using makeExecutableSchema, your resolvers would look like this:

    const resolvers = {
      Query: {
        posts: (obj, args, context, info) => {}
      },
      Post: {
        someField: (obj, args, context, info) =>{
          //obj here will be the obj a specific Post was resolved to
        }
      }
    }