Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GraphQL: Filter data in an array

I'm sure it's a simple thing to do, but I couldn't find anything in either GraphQL's doc or Graphcool's.

Say I have an entity with this schema (new GraphQL user, sorry if I make mistake in the schema representation):

Book {
  name: String!
  author: String!
  categories: [String!]
}

How would I do a query for all books that are part of the "mystery" category? I know I can filter with allBooks(filter: {}), but categories_in: ["mystery"] and categories_contains: "mystery" didn't do the trick.

like image 627
gCardinal Avatar asked Jan 06 '17 22:01

gCardinal


People also ask

Does GraphQL support filtering?

Each collection in the GraphQL schema has a filter argument which can be used to filter the results. You can filter by any custom field. Each field type supports different operators.

What is GraphQLList?

An input object type within GraphQL that represents structured inputs. class GraphQLList. A type wrapper around other types that represents a list of those types. class GraphQLNonNull. A type wrapper around other types that represents a non-null version of those types.

How do you implement search functionality in GraphQL?

Implementing Search Functionality on the Client To implement search via the client, we'll be using the data. fetchMore function from React Apollo. We'll pass a function to our component, onSearch , that will pass in the search query. onSearch will call props.


2 Answers

Category model

Thinking a bit more about this situation, creating a Category model is definitely the way to go.

For example, imagine you want to allow readers to subscribe to their favorite categories later. Or, what if you want a list of all existing categories? Using string lists, you would need to query all books and somehow postprocess all obtained categories. Handling this on a model level rather than using string lists feels much more natural.

Instead, you can create a new Category model and add a many-to-many relation between Category and Book. In situations like this, I like to add a unique enum field tag and a string field text. (A unique string field tag alone would also be suitable, probably a matter of taste.)

With this setup, you can easily fulfill data requirements like

Which books are assigned to a given category?

query {
  # query books by unique category tag
  Category(tag: MYSTERY) {
    books {
      id
    }
  }
  # query books by specific category text
  Category(filter: {
    text: "mystery"
  }) {
    books {
      id
    }
  }
}

Which books are assigned to at least one category of a given list?

query {
  allCategories(filter: {
    OR: [{
      tag: MYSTERY
    }, {
      tag: MAGIC
    }]
  }) {
    books {
      id
    }
  }
}

Which books are assigned to all categories of a given list?

query {
  allCategories(filter: {
    AND: [{
      tag: MYSTERY
    }, {
      tag: MAGIC
    }]
  }) {
    books {
      id
    }
  }
}

Related filters

Even though the above queries fulfill the specified data requirements, books are grouped by Category in the response, meaning that we would have to flatten the groups on the client.

With so called related filters, we can turn that around to only obtain books based on conditions defined its related categories.

For example, to query books assigned to at least one category of a given list:

query {
  allBooks(filter: {
    OR: [{
      categories_some: {
        tag: MYSTERY
      },
      categories_some: {
        tag: MAGIC
      }
    }]
  }) {
    id
  }
}
like image 120
marktani Avatar answered Sep 19 '22 09:09

marktani


If you are interested in using a hosted GraphQL service, scaphold.io has had this feature for a while now. All connection fields in your API come with a WhereArgs argument that exposes filters that let you really dig into your data. When you have a list of scalars like this, the WhereArgs include a contains & notContains field that allow you to filter results based off the values in your list. This allows you to make a query like this.

query MysteriousBooks($where:BookWhereArgs) {
  viewer {
    allBooks(where:$where) {
      edges { node { title, ... } }
    }
  }
}

# Variables
{
  "where": {
    "categories": {
      "contains": "mystery"
    }
  }
}

Just to be complete, you could also do a slight schema readjustment to make this work without having to filter on a scalar list. For example, you could make Category a node implementing type and then create a connection between Category and Book. Although a Book will likely not have many categories, this would allow you to issue a query like this:

query MysteriousBooks($where: CategoryWhereArgs) {
  viewer {
    allCategories(where: $where) {
      books {
        edges { node { title, ... } }
      }
    }
  }
}

# Variables
{
  "where": { 
    "name": { 
      "eq": "mystery" 
    } 
  }
}

If you structure your schema this way then you would also be able to do more filtering on the books in the category without having to loop through every book in your archive. E.G. you could efficiently ask for "all the mystery books written in the last year."

Full disclosure: I work at Scaphold and although I'd love you to try it out no hard feelings if you don't switch over. I'm excited to see people trying and loving GraphQL. If you're curious about how to implement this type of behavior on your own server let me know and I'd be happy to help there as well!

I hope this helps!

like image 35
mparis Avatar answered Sep 22 '22 09:09

mparis