How to make a client-side search engine with Vue.js and Lunr.js

Posted on January 29, 2019 in
4 min read

This is a little tutorial about making a search/filter Vue.js component using the powerful Lunr.js library.

A little disclaimer: I've purposely avoided any CSS styles also keeping minimal the HTML markup, using Vue without the CLI, to focus only on the logic and integration part for simplicity. I thought Vue.js learners may find this way useful.

A little Vue.js app

Suppose to have a little component that loads a JSON file and creates a items list based on a given array, such as:

Vue.component('mylist', {
  template:`<ul>
    <li v-for="item in list" :key="item.id">{{item.name}}</li>
</ul>`,
  props:['list']
})

Here the working example:

See the Pen vue lunr search 1 by Fabio Franchino (@abusedmedia) on CodePen.

Then, we want to integrate a search/filter capability with an input text field:

Vue.component('mysearchbtn', {
  template:`<div>
    <input type="text" placeholder="type to search"
      v-model="search"
      @input="$emit('update:search', $event.target.value)" />
</div>`,
  props:['search']
})

and here the updated example:

See the Pen vue lunr search 2 by Fabio Franchino (@abusedmedia) on CodePen.

Now we need to make both the components working together, let's say, when I type into the text field, the list should update according to the search pattern.

Welcome Lunr.js

Instead of reinventing the wheel by implementing a search algorithm, I'm going to exploit Lunr, a very powerful and configurable library that make complex search pretty neat!

Just to give a taste, you can search using some well-known patterns such as:

  • using the wildcard, i.e. Pete* to find anything that begins with pete
  • searching in a specific field object, i.e. email:*@gmail.com to find in email field the string that ends with @gmail.com
  • using operators to include or exclude specific keywords, i.e. me +you -her
  • Lunr handles plurals and articles for us as well
  • bonus tip, each result item comes with a score based on search relevance as well as additional useful information related

Setting Lunr up

Lurn requires the creation of an index based on a given dataset, such as:

var searchIndex = lunr(function () {
  this.ref('id')
  this.field('name')
  this.field('body')
  this.field('email')

  documents.forEach(doc => {
    this.add(doc)
  })
})

An important thing to consider about Lunr is the result array that is not a filtered version of the original dataset but a new and different array of objects containing specific search result properties, I guess both for performance reasons and to provide additional search information without manipulating the original array.

That means we need to find a way to filter the original array based on the produced Lunr array. This is the function I use; basically, I set a new array on every search keyword change including only the items present in the search result based on the reference field (the id in this case):

this.list = []
this.resuls.forEach(d => {
    this.original.forEach(p => {
        if(d.ref == p.id) this.list.push(p)
    })
})

I'm still wondering whether that is the best way to update the list from a performance perspective in Vue.js, though.

Now, the final examples looks like this:

See the Pen vue lunr search 3 by Fabio Franchino (@abusedmedia) on CodePen.

Hope this might be helpful to someone. I made it to integrate the functionality on a little tool I'm working on, Presenta.

Have a nice day!