Guide to Full Text Search in Meteor


It’s possible to do full text search in Meteor, as long as you’re running MongoDB 2.6 (which is included in Meteor 1.0.4 or higher). Earlier versions of MongoDB did not support text search well.

This article is written for Meteor 1.1.0.2. If you find any errors, or feel that something is out of date, please contact us!

Text Index

Before you can do a search, you need to build a text index. This is a data structure that lets MongoDB perform text searches incredibly quickly. Whenever data is inserted or removed from the indexed collection, MongoDB updates the corresponding index.

Creating a text index:

if (Meteor.isServer) {
  Messages._ensureIndex({
    "value": "text"
  });
}

The fields specified will denote the fields that are considered when a search is performed. For example, if you use the above index, and search for something, it will ignore all other properties except for value.

You may create a text index that uses multiple properties like this:

if (Meteor.isServer) {
  Messages._ensureIndex({
    "title": "text",
    "value": "text"
  });
}

Publish Method

We’ll need to handle the text search on the server, in a publish method. As of this writing, Minimongo does not support the $text operator, so doing this on the client is not possible.

The search is done using the $text operator, using this form for the query:

  { $text: {$search: 'cookies'} }

By default, the results are not ordered, and so that query will return each document that has at least one “cookies” in it’s text index. Usually you want to show the documents that rank higher first. For instance a document containing “cookies cookies cookies” should be shown before one that contains only “cookies”.

The code to sort by search rank is a bit funky, but here’s a full example of a publish method:

// If `searchValue` is not provided, we publish all known messages. If it is
// provided, we publish only messages that match the given search value.
Meteor.publish("search", function(searchValue) {
  if (!searchValue) {
    return Messages.find({});
  }
  return Messages.find(
    { $text: {$search: searchValue} },
    {
      // `fields` is where we can add MongoDB projections. Here we're causing
      // each document published to include a property named `score`, which
      // contains the document's search rank, a numerical value, with more
      // relevant documents having a higher score.
      fields: {
        score: { $meta: "textScore" }
      },
      // This indicates that we wish the publication to be sorted by the
      // `score` property specified in the projection fields above.
      sort: {
        score: { $meta: "textScore" }
      }
    }
  );
});

In general, you can typically leave the fields and sort alone.

On the Client / Subscribe

The subscription code on the client is almost identical to any other subscription, and is different only if you want to sort by search rank.

When you specify the score property in the fields projection, each document that is sent to the client will have a score property that can be used for sorting.

Here’s how our client’s subscribe code might look:

Template.search.helpers({
  messages: function() {
    Meteor.subscribe("search", Session.get("searchValue"));
    if (Session.get("searchValue")) {
      return Messages.find({}, { sort: [["score", "desc"]] });
    } else {
      return Messages.find({});
    }
  }
});

If a search is being done, we sort the published results by their score property. Otherwise, we just show them in the order that the server published them.

Example Code

We have written an example Meteor application.

There is not currently a live demo for it, as meteor.com’s deployment environment doesn’t yet support full text search.

You can clone the repository and run it locally.

Caveats

  • Each collection can only have one text index. This means that when you do a search, it will search all of the fields included in the index.
  • The index must live on the collection where the data lives. Sounds like common sense, but may have implications in your app. For example, if a message document references a user who created it, and you want to search through users based on messages created, you will need to search messages first, and then load the users.
  • Text search is not supported by minimongo. All your searches have to be done on the server.
  • If you want to use the $text operator in a Meteor.publish call, and other subscriptions which use the same collection maybe muddle your search result. Workaround: Use a Meteor.method instead
  • Only string properties or arrays of strings are supported. If you want to index properties of objects, you will have to do a workaround. We did this by keeping parallel text representations of the objects, but this does lead to extra code that must be maintained.

Other Notes

  • MongoDB text search does stemming (‘cakes’ finds ‘cake’, ‘cakes’, and ‘caking’)
  • It also does partial matches by default (searching for ‘chocolate cake’ returns all elements that match ‘chocolate’ or ‘cake’, but rates the two words together highly)
  • Exact matches can be done with quotes “chocolate cake”

References / Further Reading

Let's stay connected. Join our monthly newsletter to receive updates on events, training, and helpful articles from our team.