MongoDB Mastery
MongoDB Mastery lessons / Module 39: Text Search

Text indexes, $text and full-text search engines

Level 7: Search + Vector22 min

Keyword search with the built-in text index (stemming, stop words, phrases, negation, weights) and when you need a Lucene-based search engine instead.

This lesson is interactive. Run every example against an in-browser MongoDB, see explain() plans drawn as trees, and take the quiz.
Open the interactive lesson
You will learn to
  • Create weighted text indexes
  • Query with $text, phrases and negation, and sort by textScore
  • Know the limits of text indexes
  • Compare with $search (Lucene) and external engines

conceptThe concept

A text index tokenizes string fields, removes stop words and stems words ("running" → "run"), storing one entry per term. Query with { $text: { $search: "..." } }:

  • Words are OR-ed: "wireless headphones" matches either word.
  • "\"noise cancelling\"" requires the phrase; -refurbished excludes a term.
  • Relevance: { score: { $meta: "textScore" } } in projection and sort; weights boost fields such as titles.
  • One text index per collection (it can cover several fields, or all string fields with "$**": "text").

Full-text search engines (Lucene-based $search via mongot, Elasticsearch, OpenSearch) add fuzzy matching (typos), autocomplete, synonyms, facets, highlighting, per-field analyzers and much better relevance tuning.

whyWhy it matters

Search boxes are everywhere. A text index is enough for simple keyword search over modest data with no extra infrastructure. Knowing its limits tells you when to bring in a search engine before users complain that "headphnes" returns nothing.

Use caseReal-world scenario

An internal admin tool searches product names and tags with a text index. The public storefront, which needs typo tolerance, autocomplete and facet counts, uses a Lucene-based search index synced from MongoDB.

Try itExamples

Run and edit these in the interactive lesson.

Weighted text index on products
db.products.createIndex({ name: "text", tags: "text", category: "text" }, { weights: { name: 5, tags: 2 }, name: "products_text" })
db.products.find({ $text: { $search: "wireless speaker" } }, { name: 1, score: { $meta: "textScore" } }).sort({ score: { $meta: "textScore" } }).limit(5)
Phrases and negation
db.products.find({ $text: { $search: "\"desk lamp\"" } }, { name: 1 }).limit(5)
db.products.find({ $text: { $search: "coffee -beans" } }, { name: 1 }).limit(5)
Stemming
db.products.find({ $text: { $search: "running" } }, { name: 1 }).limit(3)
db.products.find({ $text: { $search: "runs" } }, { name: 1 }).limit(3)
Both find "Running Shoes": the query terms and the index terms are stemmed to "run".
Text search inside aggregation
db.products.aggregate([
  { $match: { $text: { $search: "pro" } } },
  { $group: { _id: "$category", matches: { $sum: 1 }, avgPrice: { $avg: "$price" } } },
  { $sort: { matches: -1 } }
])

internalsUnder the hood

Text indexes are multikey-like: one key per (term, document) with a weight. They can be large and slow to build, and every write to an indexed field updates many keys. A $text query must use the text index (you cannot hint another index) and, in a compound text index, equality conditions on prefix fields are required. Lucene-based search runs as a separate process (mongot) that reads change streams to keep its indexes eventually consistent with the collection.

Self-managed availability$search and $vectorSearch began on Atlas. Since MongoDB 8.2 they are available for self-managed Community and Enterprise deployments through mongot (initially as a public preview). Check the current status for your version before relying on them.

Avoid / preferDo this, not that

Avoid
db.products.find({ name: { $regex: "speaker", $options: "i" } })
Prefer
db.products.createIndex({ name: "text", tags: "text" })
db.products.find({ $text: { $search: "speaker" } })

A case-insensitive regex without an anchor cannot use an index efficiently and scans every name. A text index tokenises and stems words, so the search uses the index.

Avoid
db.products.find({ $text: { $search: "wireless" } })
Prefer
db.products.find({ $text: { $search: "wireless" } }, { score: { $meta: "textScore" } })
  .sort({ score: { $meta: "textScore" } })

Without sorting by score, results come back in no useful order. Users expect the best matches first.

PitfallsCommon mistakes

  • Expecting typo tolerance or prefix (autocomplete) matching from a text index.
  • Creating a second text index on the same collection (error); put all fields in one.
  • Using $regex with leading wildcards for search over large collections.
  • Forgetting language settings (default English stemming applied to other languages).

LimitsWhen not to use it

$text has no fuzzy matching, autocomplete, synonyms or facets, and only one text index per collection. For a real product search box use a search engine (Atlas Search, or Elasticsearch / OpenSearch fed by change streams). $text fits simple keyword search over modest data.

SecuritySecurity notes

A search box is untrusted input. $text treats the string as words, not operators, but still cap its length and rate-limit the endpoint. Do not add private fields (emails, notes) to a text index behind a public search API: anything indexed becomes searchable by whoever calls it.

ChallengeTry it yourself

Copy name and tags of products into search_lab, create a text index there and return the top 3 matches for "wireless" ranked by score.

Show one solution
One solution
db.search_lab.drop()
db.products.aggregate([{ $project: { name: 1, tags: 1 } }, { $out: "search_lab" }])
db.search_lab.createIndex({ name: "text", tags: "text" })
db.search_lab.find({ $text: { $search: "wireless" } }, { _id: 0, name: 1, score: { $meta: "textScore" } })
  .sort({ score: { $meta: "textScore" } })
  .limit(3)

InterviewQuestions you should be able to answer

When is a MongoDB text index enough and when do you need a search engine?

A text index is fine for simple keyword search with stemming and basic relevance on moderate data. Use Lucene-based search (mongot $search, Elasticsearch, OpenSearch) when you need fuzzy matching, autocomplete, synonyms, facets, highlighting, custom analyzers, or advanced relevance tuning, and accept the extra infrastructure and eventual consistency.

QuizCheck your understanding

4 questions with instant feedback in the interactive lesson; a wrong answer goes into your review deck.