Full-Text Search in MongoDB with Relevance Scores

Do you know about MongoDB Text Search? Imagine we are building an app and want users to search by ingredients or dish names (like chocolate or zucchini bread) and get the most relevant results. But wait! We have that information in multiple places, like the recipe title or the ingredients, or tags (like “pasta” on a linguini recipe) and even the directions too! How can we search all that data at once?

MongoDB can do that with built-in text search and relevance scoring … no extra search service or AI 🤖 required.

Setting Up a Text Index for Recipes

Here’s how to define a text index on key fields:

db.recipes.createIndex({
  title: "text",
  ingredient_text: "text",
  directions: "text",
  tags: "text"
}, {
  weights: {
    title: 10,
    ingredient_text: 5,
    directions: 2,
    tags: 3
  }
});

This setup boosts matches in the title and ingredients, while still including directions and tags for broader context. However we have a small issue, our ingredients field is actually an array of objects that look like this:

  "ingredients": [
    {
      "name": "peas",
      "quantity": {
        "amount": 1,
        "unit": "cup"
      },
      "vegetarian": true
    },
    {
      "name": "water",
      "quantity": {
        "amount": 8,
        "unit": "cup"
      },
      "vegetarian": true
    }
]

This is great for overall usage, but not so great for text searching as MongoDB’s text index doesn’t support nested fields like ingredients.name directly since it’s an array of objects, not strings.

There are a couple of ways to deal with this problem, but to keep things simple we will add a new field called ingredient_text that flattens the list of ingredient names for each document using updateMany along with $set and $map like so:

db.recipes.updateMany({}, [
  {
    $set: {
      ingredient_text: {
        $map: {
          input: "$ingredients",
          as: "i",
          in: "$$i.name"
        }
      }
    }
  }
]);

This gives us a simple array like ["milk", "onion", "salt"], which MongoDB can index with a text index.

Searching with Relevance

Once indexed, we can use the special $text operator to make searching … and get back results ranked by relevance:

db.recipes.find(
  { $text: { $search: "chocolate zucchini" } },
  { score: { $meta: "textScore" }, title: 1 }
).sort({ score: { $meta: "textScore" } });

Note: We have assigned textScore (calculated by MongoDB) to a field called score, so we can then output just the title, score and _id in our results.

Now you can search by ingredient, title, tag, or even words that appear in the cooking directions and MongoDB will return the best matches first:

{
  _id: '7599a70e-31e8-4ba1-b45c-db7fb286842e',
  title: 'Hearty Chocolate Cake',
  score: 18.833333333333336
},
{
  _id: '5ddb56a1-d182-4276-bbf3-091250f7c7c4',
  title: 'Savory Zucchini Bread',
  score: 14.476190476190476
}

Want More?

If you need typo tolerance, stemming, synonyms, or autocomplete, check out MongoDB Atlas Search … it builds on this foundation with Lucene-based power.

But for small to medium apps, this native $text search is fast, easy, and already built in.


MongoDB for Jobseekers Book

If you’re fascinated by the intricacies of MongoDB and yearn to explore its limitless possibilities further, I invite you to delve into my comprehensive book, “MongoDB for Jobseekers.”

This book is your passport to unlocking MongoDB’s full potential, whether you’re a beginner or an experienced enthusiast. Seamlessly navigate the intricacies of data management, uncover advanced techniques, and gain insights that will elevate your proficiency.

Available on Amazon and other leading platforms, “MongoDB for Jobseekers” offers a comprehensive roadmap for honing your MongoDB skills. As you embark on your journey through the world of databases, this book will be your trusted companion, guiding you towards mastery and helping you stand out in the competitive landscape.