9

I have a product and items

Product:

{
  id: Int
  style_id: Int
  items: [items]
}

Items:

{
  id: Int
  product_id: Int
  size: String
}

I want to query products but only get back products that have an item with a size.

So a query could look like this:

products(size: ["S","M"]) {
  id
  style_id
  items(size: ["S","M"]) {
    id
    size
  }
}

But it seems like there should be a way where I can just do

products {
  id
  style_id
  items(size: ["S","M"]) {
    id
    size
  }
}

And in the resolver for the products I can grab arguments from the nested query and use them. In this case add the check to only return products that have those sizes. This way I have the top level returned with pagination correct instead of a lot of empty products.

Is this possible or atleast doing it the other way around:

products(size: ["S","M"]) {
  id
  style_id
  items {
    id
    size
  }
}

And sending the size argument down to the items resolver? Only way I know would be through context but the one place I found this they said that it is not a great idea because context spans the full query in all depths.

3
  • try "items: [items!]!" in the product schema. Commented Dec 7, 2018 at 9:40
  • GraphQL doesn’t natively support what you’re asking for; if you want to filter which top-level items you get back it needs to be controlled by parameters on that top-level query field and not something lower. Commented Dec 7, 2018 at 14:40
  • 1
    @DavidMaze That's incorrect. See my answer. Commented Dec 7, 2018 at 21:35

4 Answers 4

9

I agree with @DenisCappelini's answer. If possible, you can create a new type which represents only Products that have an Item.

However, if you don't want to do that, or if you're just interested in general about how a top-level selector can know about arguments on child selectors, here is a way to do that:

There are 2 ways to do it.


To do this:

products {
  id
  style_id
  items(size: ["S","M"]) {
    id
    size
  }
}

In graphql, resolvers have this signature:

(obj, args, context, info) => {}

The 4th argument, info, contains information about the entire request. Namely, it knows about arguments on the child selectors.

Use this package, or a similar one because there are others, to parse info for you: https://www.npmjs.com/package/graphql-parse-resolve-info


The above is quite a lot of work, so if you want to do this instead:

products(size: ["S","M"]) {
  id
  style_id
  items {
    id
    size
  }
}

Then in your resolver for products, you need to also return size. Suppose this is your resolver for products:

(parent, args) => {
  ...
  return {
    id: '',
    style_id: ''
  }
}

Modify your resolver to also return size like this:

(parent, args) => {
  ...
  return {
    id: '',
    style_id: '',
    size: ["S", "M"]
  }
}

Now in your resolve for products.items, you will have access to the size, like this:

(product, args) => {
  const size = product.size
}
Sign up to request clarification or add additional context in comments.

2 Comments

didn't realize you could return fields not setup in your return object and get them on the child. That makes perfect sense and the way I am going to go about it.
The info solution is cool! IMO the second one is a little bit weird, but yeah it should work as well!
2

I found this useful #reference

//the typedef:

type Post {
    _id: String
    title: String
    private: Boolean
    author(username: String): Author
}
//the resolver:
Post: {
        author(post, {username}){
        //response
      },
    }
// usage
{
    posts(private: true){
    _id,
    title,
    author(username: "theara"){
      _id,
      username
    }
  }
}

Comments

1

IMO you should have a ProductFilterInputType which is represented by a GraphQLList(GraphQLString), and this resolver filters the products based on this list.

import { GraphQLList, GraphQLString } from 'graphql';

const ProductFilterInputType = new GraphQLInputObjectType({
  name: 'ProductFilter',
  fields: () => ({
    size: {
      type: GraphQLList(GraphQLString),
      description: 'list of sizes',
    }
  }),
});

Hope it helps :)

Comments

0

these are few tweaks you can add and make your design better and also filter items properly.

1- change your product schema:

{
  id: Int! # i would rather to use uuid which its type is String in gql.
  styleId: Int
  items: [items!] # list can be optional but if is not, better have item. but better design is below: 
  items(after: String, before: String, first: Int, last: Int, filter: ItemsFilterInput, orderBy: [ItemsOrderInput]): ItemsConnection
}

2- have a enum type for sizes:

enum Size {
    SMALL
    MEDIUM
}

3- change item schema

{
  id: Int!
  size: Size
  productId: Int
  product: Product # you need to resolve this if you want to get product from item.productId
}

4- have a filter type

input ItemFilterInput {
    and: [ItemFilterInput!]
    or: [ItemFilterInput!]
    id: Int # you can use same for parent id like productId 
    idIn: [Int!]
    idNot: Int
    idNotIn: [Int!]
    size: Size 
    sizeIn: [Size!]
    sizeNotIn: [Size!]
    sizeGt: Size # since sizes are not in alphabetic order and not sortable this wont be meaningful, but i keep it here to be used for other attributes. or you can also trick to add a number before size enums line 1SMALL, 2MEDIUM.
    sizeGte: Size
    sizeLt: Size
    sizeLte: Size 
    sizeBetween: [Size!, Size!]
}

5- then create your resolvers to resolve the below query:

{
   product {
       items(filter: {sizeIn:[SMALL, MEDIUM]}) {
           id 
       }
   }
}
# if returning `ItemsConnection` resolve it this way: 
{
    product {
        id 
        items {
            edges {
                node { # node will be an item.
                    id 
                    size
                }
            }
        }
    }
}

Relay has a very good guideline to design a better schema. https://relay.dev/ I also recommend you to add edges and node and connection to your resolvers to be able to add cursors as well. having product {items:[item]} will limit your flexibility.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.