Reputation: 1798
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
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
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
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
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