Reactive full-text search with Meteor

Recently one of our clients had a requirement for a full-text search functionality in one of his projects.

Furthermore the search should be “reactive” which means that the results for the search should appear immediately below the search form when the user enters his query into the search field.

In this blog post we’ll take a closer look how you can build that kind of full-text search for your Meteor project.

The server side

Before we start here we need to talk about the compatibility of the used features. Because we want to use the MongoDB full-text search functionality we need at least Meteor 1.0.4 and MongoDB 2.6.

Let’s start the implementation at the server side!
 The first thing we need to do is to add a MongoDB text index to the field of the collection we want to search through.
 You could do this in your database by hand, but this won’t be that good because all your developers will need to reproduce the step themselves.
 Meteor provides us a great way to add indexes programmatically with the help of the _ensureIndex function. We’ll take this approach:

// Do this on the server side<br />
Posts._ensureIndex({<br />
'title': 'text',<br />
'body': 'text'<br />
});<br />

The next thing we need to do is to add a publication which will publish all the search result. Because this publication is run on the server side we can use the MongoDB $text operator to search through our indexed fields:

Meteor.publish('searchPosts', function(query) {<br />
if (query) {<br />
return Posts.find(<br />
{ $text: {<br />
$search: query<br />
}<br />
},<br />
{<br />
fields: {<br />
score: {<br />
$meta: 'textScore'<br />
}<br />
},<br />
sort: {<br />
score: {<br />
$meta: 'textScore'<br />
}<br />
}<br />
}<br />
);<br />
} else {<br />
return Posts.find();<br />
}<br />
});<br />

Let’s dive into this code so that we understand what’s happening here.
 At first we check if a query was provided. If this is not the case, then we simply publish all posts.
 Next we use the $text-operator to search through our indexed fields (‘title’ and ‘body’) and find documents which will match the provided query. After that we make sure that we include the computed score (MongoDB will compute a score / relevancy for each document) in the returning document set and sort by that score. That’s pretty much everything what is going on here.

And that’s everything we need to do on the server side. Let’s hop over to the client.

The client side

Now that we have the server side code ready we can use our new functionality on the client side.

Let’s get the search query the user enters:

'keyup form input': _.debounce(function(event, template) {<br />
event.preventDefault();<br />
Session.set('searchQuery', template.find('form input').value);<br />
}, 300)<br />

Here we use the debounce-Function which is provided by the underscore library. This function is very handy because it enables us a efficient way to search for posts as the user types so he doesn’t need to press enter or click search. He just needs to type.
 Basically underscore will only execute the provided function if the user has stopped typing for 300ms. This way our server won’t be over flood with search queries as the user types.

Next just subscribe to the recently created publication with our search-Parameter:

var searchQuery = Meteor.subscribe('searchPosts', Session.get('searchQuery'));<br />

The last thing we need to do is to sort our results which are published to your client by the score MongoDB calculates for us:

if (Session.get('searchQuery')) {<br />
return Posts.find({}, { sort: [['score', 'desc']] });<br />
}<br />
return Posts.find();<br />

After that we can render the result as usual.

That’s it. Now we have a fully fledged full-text search functionality which makes it possible to search for posts!

Downsides

As already mentioned above you need at least Meteor 1.0.4 or higher and MongoDB 2.6. That may be no problem if you start out with a new Meteor project, but it can be a little bit cumbersome / challenging to get a legacy app updated so that it fits those requirements.

Another downside we faced was the problem that the full text search can not be combined with a geo search (See this entry in the documentation). So you have to choose which one you would like to use.
 You can implement your own “full-text search” in Meteor to circumvent this obstacle but this might be a topic for another blog post.

Additional resources

Here are some useful links if you want to dive deeper into the “Full-text search with Meteor” topic: