A text index tokenizes every string in the indexed fields, lowercases the tokens, drops stop words, stems the rest and stores one key per surviving token. $text treats your query the same way and returns documents sharing a token, scored by how many terms matched and how short the field is. One text index per collection. The corpus here is four articles with a title and a body.
db.articles.createIndex({ title: 'text', body: 'text' },
{ weights: { title: 5, body: 1 }, name: 'article_text', default_language: 'english' });
const run = q => print(q + ' -> ' + (db.articles.find({ $text: { $search: q } },
{ score: { $meta: 'textScore' }, title: 1, _id: 0 })
.sort({ score: { $meta: 'textScore' } }).toArray()
.map(d => d.title + ' (' + d.score.toFixed(2) + ')').join(' | ') || 'no match'));
run('indexing'); run('index'); run('docker volume'); run('docker -mongodb');indexing -> Indexing strategies for MongoDB (3.90) | Sharding a busy cluster (0.56) index -> Indexing strategies for MongoDB (3.90) | Sharding a busy cluster (0.56) docker volume -> Volumes, mounts and backups (4.46) | Running MongoDB in Docker (3.90) docker -mongodb -> no match
Stemming is why index and indexing give identical results, and why an article whose body says "indexes cannot fix a bad key" scores 0.56 on both. Weights multiply a field's contribution: a title hit counts five body hits, which pushes Volumes, mounts and backups — "Volumes" in its title — above Running MongoDB 1,815 in Docker 514 . Terms are OR-ed, -term excludes, "a quoted run" becomes a phrase, and a query of only stop words matches nothing. Enough for a help center, not for product search: no typo tolerance, synonyms, autocomplete, faceting or highlighting, no tuning beyond weights, and sorting on textScore forces a blocking sort.