iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Text Search

Mongos built-in $text search covers tokenisation, stemming, stop words, language-aware indexing, and basic relevance scoring. For "good enough" search on a product catalog, blog index, or knowledge base it is faster to ship than Elasticsearch — but reach for Atlas Search / Elastic when you need facets, fuzzy matching, or large-scale ranking tuning.

Create a text index, query, score, and limit fields

EXAMPLE
// 1) Single-field text index
db.products.createIndex({ name: 'text' });

// Search documents and project a relevance score
db.products.find(
  { $text: { $search: 'wool jacket' } },
  { name: 1, price_cents: 1, score: { $meta: 'textScore' } }
).sort({ score: { $meta: 'textScore' } });

// 2) Multi-field text index with field weights
db.products.dropIndex('name_text');
db.products.createIndex(
  { name: 'text', description: 'text', tags: 'text' },
  {
    weights: { name: 10, tags: 5, description: 1 },     // bigger weight = bigger contribution
    name: 'products_text_idx',
    default_language: 'english',
  },
);

// 3) Phrase + exclusion + boolean operators
db.products.find({ $text: { $search: '"linen shirt" -mens' } });
// '...'  : phrase
// -word  : exclude
// word1 word2 : OR by default; each word is a separate term

// 4) Language switch per query (e.g. a French catalog)
db.products.find({ $text: { $search: 'chemise lin', $language: 'french' } });

// 5) Score-only sort
db.products.find(
  { $text: { $search: 'wool jacket' } },
  { score: { $meta: 'textScore' }, name: 1 }
).sort({ score: { $meta: 'textScore' } }).limit(20);

// 6) Combine with non-text predicates (uses BOTH indexes when possible)
db.products.find({
  $text:   { $search: 'jacket' },
  status:  'active',
  price_cents: { $lte: 50000 },
});

// 7) Inside an aggregation pipeline — $facet for search results + facets
db.products.aggregate([
  { $match: { $text: { $search: 'wool jacket' }, status: 'active' } },
  { $facet: {
      hits: [
        { $addFields: { score: { $meta: 'textScore' } } },
        { $sort:      { score: { $meta: 'textScore' }, _id: 1 } },
        { $limit:     20 },
        { $project:   { name: 1, price_cents: 1, score: 1 } },
      ],
      total:      [ { $count: 'value' } ],
      categories: [
        { $group: { _id: '$category', count: { $sum: 1 } } },
        { $sort: { count: -1 } }, { $limit: 10 },
      ],
  } },
]);

// 8) Inspect the planner
db.products.find({ $text: { $search: 'jacket' } }).explain('executionStats');
// Look for stage 'TEXT' or 'TEXT_OR'; rows examined should be small.

// 9) Limits and gotchas
// - Only ONE text index per collection (covers multiple fields).
// - Case- and diacritic-insensitive by default (good for product names).
// - Tokenisation is space + punctuation; CJK and other languages need
//   external pipelines (or Atlas Search).
// - $text + $or with non-text predicates may not use the index efficiently;
//   restructure into a pipeline ($match -> $text first).
// - No partial token / prefix matching out of the box; use Atlas Search.

// 10) When to graduate
// - Need typo tolerance / fuzziness         -> Atlas Search (Lucene-backed)
// - Need synonyms / per-document boosts      -> Atlas Search
// - Search index size > few GB               -> Elastic / OpenSearch
// - Already heavy in OpenSearch              -> stay there

Why it matters

$text is the right answer for "make this product / docs collection searchable in an afternoon". The moment you need typo tolerance, synonyms, faceted UI, or > a few GB of indexed text, move to Atlas Search or a dedicated engine; trying to push $text past its design space ends with brittle hacks that are harder to maintain than the migration would have been.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
db.posts.createIndex({ title: 'text', body: 'text' });
db.posts.find({ $text: { $search: 'mongo atlas' } });
Try it Yourself »

Discussion

Loading…