GraphQL Queries - Filters
Keystone provides a powerful GraphQL API for querying the data in your system. At the core of this API are query filters. This guide will show you how to use filters to get data you need.
The filter references in this guide are based on the schema defined in the Task Manager example project.
Scalar Filters
If we want to find all the Tasks
in our system, we can use the query tasks()
.
{tasks {idlabel}}
In general we don't want to grab all the tasks at once, but we want to find a particular set of tasks which match a certain condition. In Keystone we can do this by passing a where
argument to the tasks()
query.
If we want to find all the tasks with a label equal to "Hello"
we can write:
{tasks(where: { label: { equals: "Hello" } }) {idlabel}}
Keystone provides a wide range of different filters. If we want to find all those tasks which don't have a label of "Hello"
we can write:
{tasks(where: { label: { not: { equals: "Hello" } } }) {idlabel}}
The text()
field type also supports searching for sub-strings within a field:
{tasks(where: { label: { contains: "He" } }) {idlabel}}
Different field types support different filters. The field finishBy: timestamp()
, for example, lets you filter by tasks with a due date after a particular point in time:
{tasks(where: { finishBy: { gt: "2022-01-01T00:00:00.000Z" } }) {idlabel}}
For a full list of the different filters provided by each field type, please check the Query Filter API.
For more complex queries, you can combine multiple filters, and only those items which match all conditions will be returned.
{tasks(where: {label: { contains: "He" },finishBy: { gt: "2022-01-01T00:00:00.000Z" }}) {idlabel}}
Combined Filters
For more advanced queries, you might need to explicitly combine different filters.
AND
The AND
operater accepts a list of sub-filters, and will only return those items which match all of these conditions.
{tasks(where: { AND: [{ label: { contains: "H" } },{ label: { contains: "ll" } }] }) {idlabel}}
OR
The OR
operater accepts a list of sub-filters, and will only return those items which match at least one of these conditions.
{tasks(where: { OR: [{ label: { contains: "H" } },{ label: { contains: "ll" } }] }) {idlabel}}
NOT
The NOT
operater accepts a list of sub-filters, and will only return those items which don't match the conditions. You'll generally only pass a single filter to NOT
rather than a list but if you do a pass a list, they're AND
ed together.
{tasks(where: { NOT: {label: { contains: "H" }} }) {idlabel}}
Relationship Filters
As well as filtering by scalar fields, you can also filter against relationship fields. The Relationship filters available depend on the number of item that can exist on the far side of the relationship, that is, whether you have many: true
or many: false
configured on the field.
To-One
If you have many: false
configured on the relationship field, then you can find items based on a where
filter using the fields from the related list.
For example, to find all the tasks where the task is assigned to a user named "Alice"
, we can run the following query.
{tasks(where: { assignedTo: { name: { equals: "Alice" } } }) {idlabel}}
To find any tasks that have no assigned user, we can run the following query.
{tasks(where: { assignedTo: null }) {idlabel}}
Conversely, if we wanted tasks where any user was assigned, we can invert the above using the NOT
operator:
{tasks(where: { NOT: { assignedTo: null } }) {idlabel}}
To-Many
If you have many: true
configured on the relationship field, then you can find items based on whether some
, none
, or every
of the related items match a condition. The condition itself can be a where
filter using the fields from the related list or an empty object ({}
) to indicate all related items should be matched.
Some
The some
filter returns true when at least one of the related items match the conditions supplied.
For example, to find people that have one or more tasks where the task label contains "shopping"
, we can run the following query:
{people(where: { tasks: { some: { label: { contains: "shopping" } } } }) {idname}}
To find people with any tasks at all, we could can pass an empty object. This indicate that no conditions should be applied to tasks so all tasks are matched:
{people(where: { tasks: { some: {} } }) {idname}}
None
The none
filter returns true when there are zero related items that match the conditions supplied.
For example, if we had an urgent task we were looking to assign, we could query for people who didn't have any task that were both incomplete and high-priority:
{people(where: { tasks: { none: { priority: { equals: high }, isComplete: { equals: false } } } }) {idname}}
Or, if we wanted people with no assigned tasks at all (not even completed tasks), we could pass none
as an empty object:
{people(where: { tasks: { none: {} } }) {idname}}
Every
The every
filter returns true when all related items match the conditions supplied.
For example, if we wanted to find people who have completed their assigned tasks, we could write:
{people(where: { tasks: { every: { isComplete: { equals: true } } } }) {idname}}
Note the results will also include people with no tasks assigned at all. If you only wanted people who have both completed their assigned tasks and actually have at least one task assigned, you could add a some
filter:
{people(where: { tasks: { every: { isComplete: { equals: true } }, some: {} } }) {idname}}
Like the other "to-many" relationship filters, every
does also accept an empty object, indicating no filters should be applied to the related items matched. However, doing so has no effect – it always evaluates to true, regardless of the properties of related items or whether they exist at all. So a query like this:
{people(where: { tasks: { every: {} } }) {idname}}
Could be re-written as simply:
{people {idname}}
Filtering Related Items
In the relationship examples above we have been filtering items based on their related items, but we haven't been loading any information about the related items themselves. GraphQL gives us the ability to write queries that retrieve data across a graph of interconnected items - in Keystone, relationships form the links between these items. Queries that traverse these links apply filters both the top-level "list" fields (ie. tasks
and people
in these examples) and when selecting data about related items themselves.
This is easier to explain with examples so let's expand on one from earlier. Lets say we want to find people who have no high-priority, incomplete tasks but we want to see all the incomplete tasks they still have on their list. We could write something like this:
{# Only return people who have no high-priority, incomplete taskspeople(where: {tasks: {none: {priority: { equals: high },isComplete: { equals: false }}}}) {idname# For each person, get tasks that are not completetasks(where: {isComplete: { equals: false }}) {idlabel}}}
A good way to explore what's possible within your Keystone GraphQL API is using the GraphQL Playground that should be available on your GraphQL endpoint in dev (ie. at http://localhost:3000/api/graphql
).