BrinkDaDrink
BrinkDaDrink

Reputation: 1798

GraphQL using nested query arguments on parent or parent arguments on nested query

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.

Upvotes: 9

Views: 29955

Answers (4)

DragonKnight
DragonKnight

Reputation: 1870

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.

Upvotes: 0

Tun Cham Roeun
Tun Cham Roeun

Reputation: 164

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
    }
  }
}

Upvotes: 2

Croolsby
Croolsby

Reputation: 1466

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
}

Upvotes: 9

deniscapp
deniscapp

Reputation: 820

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 :)

Upvotes: 1

Related Questions