DocsHub
Advanced

Atlas Search

Learn how Atlas Search brings full-text search powered by Apache Lucene to MongoDB, including fuzzy matching and autocomplete.

Atlas Search

In the Querying section, we used $text with a text index to search course titles and descriptions. $text works, but it is basic — no typo tolerance, limited ranking, no autocomplete.

Atlas Search is MongoDB's built-in full-text search engine, available on MongoDB Atlas. It is built on Apache Lucene — the same technology behind Elasticsearch — and brings powerful search features directly into your aggregation pipeline, with no separate search service to manage.


Atlas Search vs $text

$textAtlas Search
AvailabilityAny MongoDB deploymentAtlas only
EngineMongoDB's built-in text indexApache Lucene
Typo tolerance (fuzzy matching)❌ No✅ Yes
Autocomplete❌ No✅ Yes
Relevance scoringBasicAdvanced — Lucene scoring
Highlighting matched terms❌ No✅ Yes
Multiple analyzers (language-aware)❌ No✅ Yes
SetupcreateIndex({ field: "text" })Search index created in Atlas UI or via API

If your app is hosted on Atlas — which we covered in the Basics section — Atlas Search is available with no additional infrastructure.


Creating a Search Index

Search indexes are different from regular MongoDB indexes — they are created and managed through the Atlas UI, the Atlas CLI, or the createSearchIndex command.

Via mongosh

db.courses.createSearchIndex(
  "courses_search",
  {
    mappings: {
      dynamic: false,
      fields: {
        title: {
          type: "string",
          analyzer: "lucene.standard"
        },
        description: {
          type: "string",
          analyzer: "lucene.standard"
        }
      }
    }
  }
)

dynamic: false means only the fields you explicitly list are indexed for search. Set dynamic: true to automatically index all string fields — simpler to start, but less control.

Via Atlas UI

  1. Go to your cluster in Atlas
  2. Click Search in the left sidebar
  3. Click Create Search Index
  4. Choose the courses collection
  5. Define field mappings — same structure as above
  6. Click Create

Index creation takes a minute or two. Once it shows "Active", you can use it.


The $search Aggregation Stage

Atlas Search is used through a $search stage — always as the first stage in an aggregation pipeline.

db.courses.aggregate([
  {
    $search: {
      index: "courses_search",
      text: {
        query: "computer science",
        path: "title"
      }
    }
  }
])

This searches the title field for documents matching "computer science" — using Lucene's relevance scoring, not just exact substring matching.

Search Across Multiple Fields

db.courses.aggregate([
  {
    $search: {
      index: "courses_search",
      text: {
        query: "programming",
        path: ["title", "description"]
      }
    }
  }
])

Fuzzy Matching — Typo Tolerance

This is one of the biggest advantages over $text. Atlas Search can match queries even when the user makes a typo.

// User searches "compter scince" (typos for "computer science")
db.courses.aggregate([
  {
    $search: {
      index: "courses_search",
      text: {
        query: "compter scince",
        path: "title",
        fuzzy: {
          maxEdits: 2,       // allow up to 2 character edits
          prefixLength: 2    // first 2 characters must match exactly
        }
      }
    }
  }
])

Result — still finds "Computer Science" courses, despite the typos.

maxEdits controls how many character changes (insertions, deletions, substitutions) are allowed — 1 or 2 is typical. prefixLength requires the beginning of the word to match exactly, which improves both accuracy and performance.

Fuzzy matching is what makes search feel "smart" to users — they do not need to spell things perfectly. This is impossible with $text, which requires whole-word matches with no typo tolerance.


Relevance Scoring

Every result from $search includes a relevance score, accessible via $meta: "searchScore":

db.courses.aggregate([
  {
    $search: {
      index: "courses_search",
      text: {
        query: "computer science",
        path: ["title", "description"]
      }
    }
  },
  {
    $project: {
      title: 1,
      description: 1,
      score: { $meta: "searchScore" }
    }
  },
  { $sort: { score: -1 } }
])

Result:

[
  { title: "Computer Science Fundamentals", score: 4.82 },
  { title: "Intro to Programming",          score: 2.15 },
  { title: "Computer Networks",             score: 1.93 }
]

Higher scores mean stronger matches — a course titled "Computer Science Fundamentals" scores higher for the query "computer science" than one that only mentions "computer" in its description.


Autocomplete

Atlas Search has a dedicated autocomplete field type and operator — perfect for search-as-you-type features.

Define an Autocomplete Field

db.courses.createSearchIndex(
  "courses_autocomplete",
  {
    mappings: {
      dynamic: false,
      fields: {
        title: {
          type: "autocomplete",
          tokenization: "edgeGram",
          minGrams: 2,
          maxGrams: 10
        }
      }
    }
  }
)

Using Autocomplete

// User has typed "Comp" so far
db.courses.aggregate([
  {
    $search: {
      index: "courses_autocomplete",
      autocomplete: {
        query: "Comp",
        path: "title"
      }
    }
  },
  { $limit: 5 },
  { $project: { title: 1, _id: 0 } }
])

Result:

[
  { title: "Computer Science Fundamentals" },
  { title: "Computer Networks" },
  { title: "Composition and Writing" }
]

As the user types more characters, send updated queries — the results narrow down in real time, exactly like search suggestions on any modern website.


Highlighting Matches

Atlas Search can return information about exactly which parts of the text matched — useful for showing users "why" a result matched, with the matched terms highlighted.

db.courses.aggregate([
  {
    $search: {
      index: "courses_search",
      text: {
        query: "programming",
        path: "description"
      },
      highlight: {
        path: "description"
      }
    }
  },
  {
    $project: {
      title: 1,
      description: 1,
      highlights: { $meta: "searchHighlights" }
    }
  }
])

The highlights field contains the matched snippet broken into parts you can render with <mark> tags or similar in your frontend.


Combining $search with Other Stages

$search must be the first stage, but you can follow it with any normal aggregation stages — $match, $group, $lookup, $project, and so on.

// Search for "math" courses, only in 10th grade, with teacher details
db.courses.aggregate([
  {
    $search: {
      index: "courses_search",
      text: { query: "math", path: "title" }
    }
  },
  {
    $match: { grade: "10th" }
  },
  {
    $lookup: {
      from: "teachers",
      localField: "teacherId",
      foreignField: "_id",
      as: "teacher"
    }
  },
  { $unwind: "$teacher" },
  {
    $project: {
      title: 1,
      grade: 1,
      teacherName: "$teacher.name",
      score: { $meta: "searchScore" }
    }
  },
  { $sort: { score: -1 } }
])

Atlas Search handles the text relevance ranking; standard aggregation stages handle filtering, joining, and shaping — all in one pipeline.


Here is the complete upgrade from $text (Querying section) to Atlas Search with fuzzy matching and autocomplete:

Search Index Setup

// Full-text search with fuzzy matching support
db.courses.createSearchIndex(
  "courses_search",
  {
    mappings: {
      dynamic: false,
      fields: {
        title: { type: "string", analyzer: "lucene.standard" },
        description: { type: "string", analyzer: "lucene.standard" }
      }
    }
  }
)

// Separate index for autocomplete on title
db.courses.createSearchIndex(
  "courses_autocomplete",
  {
    mappings: {
      dynamic: false,
      fields: {
        title: {
          type: "autocomplete",
          tokenization: "edgeGram",
          minGrams: 2,
          maxGrams: 10
        }
      }
    }
  }
)

Search Endpoint — With Fuzzy Matching

app.get('/api/courses/search', async (req, res) => {
  const { q } = req.query;

  const results = await db.collection('courses').aggregate([
    {
      $search: {
        index: "courses_search",
        text: {
          query: q,
          path: ["title", "description"],
          fuzzy: { maxEdits: 1, prefixLength: 2 }
        }
      }
    },
    { $limit: 10 },
    {
      $project: {
        title: 1,
        description: 1,
        grade: 1,
        score: { $meta: "searchScore" }
      }
    }
  ]).toArray();

  res.json(results);
});

Autocomplete Endpoint

app.get('/api/courses/autocomplete', async (req, res) => {
  const { q } = req.query;

  if (!q || q.length < 2) {
    return res.json([]);
  }

  const suggestions = await db.collection('courses').aggregate([
    {
      $search: {
        index: "courses_autocomplete",
        autocomplete: { query: q, path: "title" }
      }
    },
    { $limit: 5 },
    { $project: { title: 1, _id: 0 } }
  ]).toArray();

  res.json(suggestions);
});
GET /api/courses/search?q=compter scince
→ still finds "Computer Science" courses despite typos

GET /api/courses/autocomplete?q=Comp
→ ["Computer Science Fundamentals", "Computer Networks", ...]

Quick Reference

FeatureSyntax
Create search indexdb.collection.createSearchIndex(name, definition)
Basic text search{ $search: { text: { query, path } } }
Multi-field searchpath: ["title", "description"]
Fuzzy matchingfuzzy: { maxEdits: 1, prefixLength: 2 }
Relevance score{ $meta: "searchScore" }
Autocomplete fieldtype: "autocomplete", tokenization: "edgeGram"
Highlightinghighlight: { path: "field" }{ $meta: "searchHighlights" }

Start with $text for simple keyword search — it works everywhere and requires no extra setup. Move to Atlas Search when your users need typo tolerance, autocomplete, or when search relevance quality becomes a real product concern. The aggregation pipeline structure stays familiar — $search is just a new first stage that plugs into everything else you already know.

On this page