Strapi provides you with multiple ways to filter results on relation’s fields deeply. This article will be covering the different ways to filter results using the following APIs:
Before you can jump into this content, you need to have the following:
The following example will be used to demonstrate the deep filtering capabilities of Strapi:
A collection type called Book
with following fields:
title
authors
Another collection type is called Author
with the following fields:
name
hobby
books
Books
can have multiple Authors
, and Authors
can write multiple Books
and have different Hobbies
.
The end goal is to be able to filter Books
based on their Author's
Hobbies
. Some dummy data will help us explore different approaches in a practice way. Consider four authors:
1| ID | NAME | HOBBY |
2| -- | ------- | ----- |
3| 1 | Author1 | play |
4| 2 | Author2 | sing |
5| 3 | Author3 | dance |
6| 4 | Author4 | dance |
Author 1’s hobby is to play
, Author 2’s is to sing
, Authors 3 & 4’s hobby is to dance
.
Consider the following books:
1| ID | TITLE | AUTHORS |
2| -- | ------------------ | ------- |
3| 1 | How to Play | Author1 |
4| 2 | How to Sing | Author2 |
5| 3 | How to Dance | Author3 |
6| 4 | How to be Flexible | Author4 |
A practical use-case can be to find all the books written by authors whose hobby contains the word dance in it, and this is what we will explore in this article.
The following API Request will return all the books
written by authors
whose hobby contains the word dance
:
Sample Request:
1 GET /api/books?filters\[authors\][hobby][$contains]=dance
Sample Response:
1 {
2 "data":[
3 {
4 "id":3,
5 "attributes":{
6 "title":"How to Dance",
7 ...
8 }
9 },
10 {
11 "id":4,
12 "attributes":{
13 "title":"How to be Flexible",
14 ...
15 }
16 }
17 ]
18 }
Queries can accept a filters
parameter with the following syntax:
GET /api/:pluralApiId?filters\[field\][operator]=value
Strapi supports a huge range of operators in a query. A comprehensive list of query operators can be found in the Strapi documentation.
Let’s dissect the example request:
GET /api/books?filters\[authors\][hobby][$contains]=dance
/api/books
part without any query parameters would return all books (limited by the default set limit).?filters
parameter offers the ability to filter results.[authors]
is a relation field in the Book
collection type.[hobby]
is a field in the Author
collection type.[operator]
can contain an operator that Strapi supports.=dance
is the value .The same query can be programmatically constructed at the frontend:
1 const qs = require('qs');
2 const query = qs.stringify({
3 filters: {
4 authors: {
5 hobby: {
6 $contains: dance,
7 },
8 },
9 },
10 }, {
11 encodeValuesOnly: true,
12 });
13
14 await request(`/api/restaurants?${query}`);
15 // GET /api/restaurants?filters\[authors\][hobby][$contains]=dance
The Query Engine API offers the ability to filter results found with its findMany() method.
Results are filtered with the filters
parameter, which accepts logical operators and attribute operators. Every operator should be prefixed with $
.
The query equivalent of GET /api/books?filters\[authors\][hobby][$contains]=dance
would be as follows:
1 module.exports = {
2 async index(ctx, next) {
3 const entries = await strapi.db.query('api::book.book').findMany({
4 where: {
5 authors: {
6 hobby: {
7 $contains: 'Dance'
8 },
9 }
10 },
11 });
12 ctx.body = entries;
13 }
14 };
In order to try out the above query, you may want to create a controller, like the way it is done in the video below.
dancebooks
: npx strapi generate
1 ? Strapi Generators controller - Generate a controller for an API
2 ? Controller name dancebooks
3 ? Where do you want to add this controller? Add controller to new API
4 ✔ ++ /api/dancebooks/controllers/dancebooks.js
1 src/api/dancebooks/routes
dancebooks.js
and add the following route to it:1 module.exports = {
2 routes: [
3 {
4 method: 'GET',
5 path: '/dance-books',
6 handler: 'dancebooks.index'
7 }
8 ]
9 }
src/api/dancebooks/controllers/dancebooks.js
with the following lines of code:
'use strict';1 module.exports = {
2 async index(ctx, next) {
3 const entries = await strapi.db.query('api::book.book').findMany({
4 where: {
5 authors: {
6 hobby: {
7 $contains: 'Dance'
8 },
9 }
10 },
11 });
12 ctx.body = entries;
13 }
14 };
Once the server restarts and a GET request to /api/dance-books
is made, the expected response is received. The permission needs to be granted to the public to access the route.
1 {
2 "data":[
3 {
4 "id":3,
5 "attributes":{
6 "title":"How to Dance",
7 ...
8 }
9 },
10 {
11 "id":4,
12 "attributes":{
13 "title":"How to be Flexible",
14 ...
15 }
16 }
17 ]
18 }
The exact result can be achieved using the Entity Service API. Replace the code in src/api/dancebooks/controllers/dancebooks.js
with the following:
1 'use strict';
2
3 module.exports = {
4 async index(ctx, next) {
5 const entries = await strapi.entityService.findMany('api::book.book', {
6 filters: {
7 authors: {
8 hobby: {
9 $contains: 'Dance'
10 },
11 }
12 },
13 });
14 ctx.body = entries;
15 }
16 };
The Entity Service API is built on the Query Engine API. The Entity Service is the layer that handles Strapi's complex data structures like components and dynamic zones, and uses the Query Engine API under the hood to execute database queries.
The following GraphQL query will give back all the books whose author’s hobby contains the word “dance”:
1 query {
2 books (filters: { authors:{hobby : { contains: "dance" }}} ){
3 data {
4 id
5 attributes {
6 authors {
7 data {
8 attributes {
9 hobby
10 }
11 }
12 }
13 }
14 }
15 }
16 }
The playground should look like the image below:
This article demonstrated how to filter on deeply-nested data using relationship fields in Strapi. You can learn more about these exciting features of Strapi in this excellent blog: Announcing Strapi V4. Let me know you have any suggestions and what you will be building with the knowledge.