Search within relation content type

System Information
  • Strapi Version: v3.1.4
  • Operating System: Mac OS Catalina 10.15.7
  • Database: MongoDB
  • Node Version: v12.17.0
  • NPM Version: 6.14.4
  • Yarn Version: 1.22.5

Hi There,

I have a simple content-type “Onts”. “Onts” has a few relationships to other content-type, for example, “address_unit” or “services”. See screenshot below.

When I run a search query with Postman or the built-in Swagger platform, I successfully retrieve search results from any fields within the “Onts” document, however, I am unable to search within the relational documents.

See screenshots above.

Third Screenshot shows that I have success in searching for “5A594F43806616F8”.
However, with Fourth screenshot, I am trying to search “106” (which is the “unit_no” in “address_unit”) but with no success.

I have also tried searching for “5f01f63a408c5c5d59a04037” (which is the “vlan” in “services”) with no success.

I understand I will need to build a custom controller and services function in Strapi.
This is my current “/api/onts/services/onts.js” file:

module.exports = {
  search: async (params) => {
    return strapi.query("onts").search(params);
  },
};

and this is my /api/onts/controllers/onts.js" file:

module.exports = {
  async find(ctx) {
    let entities;
    if (ctx.query._q) {
      entities = await strapi.services.onts.search(ctx.query);
    } else {
      entities = await strapi.services.onts.find(ctx.query);
    }

    return entities.map((entity) =>
      sanitizeEntity(entity, { model: strapi.models.onts })
    );
  },
};

How do I go about in modifying the code to be able to search in relational documents as well?

Any assistance is appreciated.

Warm Regards,

Ooooo alright this could get very complicated, as the way our _q search parameter works is by looking at the full-text indexes (on SQL at least, will need to review the Mongo connector to be sure).

Let me poke @alexandrebodin or @Convly / @Pierre_Noel to see if they have any thoughts before I dive down what could be very complicated example.

Hello :slight_smile:
It is not a full-text search anymore, it’s a basic search “%mysearch%” done on all fields (except relational ones) :slight_smile:
Doing a search on relational fields can lead to performance issues so I recommand to use a search provider like algolia or elastic seach.
However, if you want to implement a search only for onts and only for some of its relations:

I hope it helps ! :slight_smile:

Thanks @DMehaffy and @Pierre_Noel,

My application is so simple, it feels like overkill to implement a solution such as Algolia or Elastic Search, but will keep it in mind.

For now, I will try to implement a custom query as proposed here:

PS: With a previous project of mine, I implemented an aggregation pipeline that allowed me to retrieve a document from MongoDB, with the relational documents looked up via $lookup, ie one big flat document with all the fields resolved from all the related documents. Then I implemented a search function client-side. But this approach obviously has some drawbacks. For example, you need to retrieve all the documents from the DB, which could lead to slow API responses, plus doing the search client-side also had an impact on performance.

Anyway, let me get going on that custom query. Will provide an update later.

1 Like

So I managed to implement a hacked-together solution on the Backend.
Whether this is the correct way of solving this issue, I don’t know. In fact, I doubt it.
Best I could come up with.

Problem:

Extend the find/search function to search with relational documents in MongoDB


Solution:

  1. Retrieve populated documents. (Main document and their relational documents)
  2. Search through the array of documents and store result.
  3. Return the result as a response.
// in services file - /api/onts/services/onts.js

const pop = [
  {
    path: "address_unit",
    populate: {
      path: "address_complex",
    },
  },
  {
    path: "services",
    populate: {
      path: "vlan",
    },
  },
];

module.exports = {
  customFind: async (params, populate) => {
    let results;
    let filteredResults;
    results = await strapi.query("onts").find(null, pop);  // Step 1
    filteredResults = searchArrayOfObjects(results, params);  // Step 2
    return filteredResults;  // Step 3
  },
};
// in controllers file - /api/onts/controllers/onts.js
module.exports = {
  customFind: async (ctx) => {
    let result;
    result = await strapi.services.onts.customFind(ctx.params.find);
    return result;
  },
};
// in routes file - /api/onts/config/routes.json

{
  "routes": [
    {
      "method": "GET",
      "path": "/onts/:find",
      "handler": "onts.customFind",
      "config": {
        "policies": []
      }
    },
    ... //other routes
 ]
}

For those interested in knowing what search function I used in Step 2 searchArrayOfObjects(results, params), here is the code.

function searchSingleObject(object, text) {
  let flag;
  for (let key in object) {
    if (object[key] !== null) {
      if (typeof object[key] === "object") {
        if (object[key].length > 0 || object[key].length !== undefined) {
          flag = searchArrayOfObjects(object[key], text);
        }
        flag = searchSingleObject(object[key], text);
        if (flag) break;
      } else {
        flag = false;
        flag =
          object[key].toString().toLowerCase().indexOf(text.toLowerCase()) > -1;
        if (flag) break;
      }
    }
  }

  return flag;
}

function searchArrayOfObjects(objectList, text) {
  if (undefined === text || text === "" || text === undefined)
    return objectList;
  const res = objectList.filter((object) => {
    return searchSingleObject(object, text);
  });
  return res;
}

Anyway, it works.
Please let me know if there are better solutions available.

1 Like

Ah poof.
I realized that the function in Step 2 only respond with 100x records. As a result only those 100 will be searched. Seeing as I have more than 100 records, this solution is basically useless. :man_facepalming: :rofl:

Depending on the ORM/Database you are using (I believe it was mongodb) you can directly hook into the ORM and execute the search that way: https://strapi.io/documentation/v3.x/concepts/queries.html#mongoose

1 Like

Thanks @DMehaffy busy looking into it.

PS, is there a way to increase the default API limit from 100 to say 500?

As @DMehaffy said you can hook into the ORM and execute a custom query.

strapi.query('modelName').model.limit(500)

You can find more details in the mongoose documentation.

For for our queries you can pass in the limit filter

strapi.query('modelName').find({ limit: 500 })

Aahhh Thanks @DMehaffy and @sunnyson