Search code examples
node.jsmongodbmongoose

How to perform full text search in mongoose aggregrate


Given a mongoose object. How can I do a full text search in document object

 {
    "product" :[
        {
            "_id": "633d32c4ea9b06177e183388",
            "document": {
                "_id": "633d32c4ea9b06177e183388",
                "category": [
                    "62bacb137e6e0b6ac850b9ce",
                    "633d19260b72ba127d928e74"
                ],
                "name": "Iphone 13",
                "slug": "iphone-13",
                "__v": 0,
                "price": 2000.5,
                "images": [
                    {
                        "_id": "63cca1a271c3635246eee937",
                        "url": "https://res.cloudinary.com/..."
                    }
                ],
                "sold": 15,
                "ratings": []
            },
            "avgRating": null
        },
    ...

]


}

This is what I have tried but it doesn't work

const product = await Product.aggregate([
  {
    $project: {
      document: "$$ROOT",
      avgRating: {
        $floor: { $avg: "$ratings.star" },
      },
    },
  },

  {
    $match: {
      document: { $text: { $search: `"${searchText}"` } },
    },
  },
]);

This is my product Model

const product = {
  name: {
    type: String,
    required: true,
    trim: true,
    minlength: [2, "Too short"],
    maxlength: [32, "Too long"],
    unique: true,
  },
  slug: {
    type: String,
    unique: true,
    lowercase: true,
    index: true,
  },
  category: [
    {
      type: ObjectId,
      ref: "Category",
    },
  ],
  description: {
    type: String,
    maxlength: 200,
  },
  price: {
    type: Number,
    required: true,
    trim: true,
    maxlength: 32,
  },
  shipping: {
    type: String,
    enum: ["Yes", "No"],
  },
  color: [
    {
      type: String,
      enum: ["Black", "Brown", "Silver", "White", "Blue"],
    },
  ],
  sold: {
    type: Number,
    default: 0,
  },
  quantity: {
    type: Number,
  },
  images: [
    {
      public_id: String,
      url: String,
    },
  ],
  ratings: [
    {
      star: Number,
      postedBy: {
        type: ObjectId,
        ref: "User",
      },
    },
  ],
};

The goal is to aggregate product ratings by getting the average of product ratings star then chain multiple where clauses , one of the where clause is to perform a full text search in the document object.

Kindly advise.

Thank you.


Solution

  • $text requires a text index on the field, whose contents are to be searched. In your case, you are using $text on a document, rather than a text field, so most likely it will give unexpected results. One way to solve this is to add a field in your schema let's say textContent, which is a stringified version of your document, and then create a text index on this field. So your pipeline would become something like this:

    const product = await Product.aggregate([
      {
        $match: {
          textContent: { $text: { $search: `"${searchText}"` } },
        },
      },
      {
        $project: {
          avgRating: {
            $floor: { $avg: "$ratings.star" },
          },
        },
      },
    ]);
    

    Note: I have moved $match above because it's expected to be the first stage of the pipeline. Refer to $text documentation here.