Michiel Mulders Michiel loves the NodeJS and Golang programming languages. A backend/core blockchain developer and avid writer, he's very passionate about blockchain technology.

MeiliSearch: A definitive guide

7 min read 2233

MeiliSearch-A-Definitive-Guide

MeiliSearch is an open-source, blazing-fast, and hyper-relevant search engine. But it’s not just any search engine: MeiliSearch’s highly customizable search engine API gives you a ton of flexibility. For instance, you can modify ranking rules, add custom ranking rules, configure synonyms, filter out stop words, and more. To improve your searching capabilities, MeiliSearch allows you to set facet filters that act as an index.

This tutorial demonstrates how to interact with a MeiliSearch instance through the MeiliSearch JS client. You can use this client for any frontend project that requires fast searching capabilities.

Here’s what we’ll cover:

We’ll be using both the MeiliSearch JavaScript client and direct API requests for quick operations. Without further ado, let’s get started.

Installing MeiliSearch

Make sure you have an active Node.js installation. It’s also useful to have a tool such as cURL to send API requests.

Next, we need a MeiliSearch instance to interact with. We have several possibilities to run a MeiliSearch instance:

  1. Use the MeiliSearch sandbox to create a temporary instance for 72 hours
  2. Deploy an instance using a Digital Ocean droplet
  3. Run MeiliSearch using Docker
  4. Alternatively, the MeiliSearch documentation provides installation options for Debian, Ubuntu, Linux, and Mac OS

To secure our instance, we need to set a master key that protects the MeiliSearch instance’s API endpoints. By default, the Meilisearch sandbox will provide you with one. For options no. 2, 3, and 4, you have to set a master key manually.

To verify your installation, try sending the following request to list all indexes. A fresh installation should have no indexes. Therefore, the response is an empty array.

Make sure you replace the IP address with your IP address. For installations using DigitalOcean or the MeiliSearch sandbox, we don’t need to append the port number :7700.

curl http://127.0.0.1:7700/indexes \
    --header 'X-Meili-API-Key: your-master-key'
// => []

Next, let’s prepare the project setup.

We made a custom demo for .
No really. Click here to check it out.

MeiliSearch project setup

First, create a new project using npm:

npm init -y

Next, add the meilisearch-js dependency:

npm install meilisearch

Finally, create an index.js file that contains all our code. Be sure to create this file in the root of your newly created project.

touch index.js

Popular blogs data set

We’re using a modified dataset titled “Internet news data with readers engagement” for this tutorial. This data set has been uploaded to Kaggle by Szymon Janowski.

Since we don’t need all properties from this dataset, we’ve created a modified dataset that fits our tutorial. You can download the dataset on GitHub or use the command below to download the dataset directly:

curl -L https://gist.githubusercontent.com/michielmulders/921b0e1f292519118cfc5ee368f0f663/raw/c62cef304a50d883ca88bf95b47c809a873ce3ba/blogs.json -o blogs.json

A blog object contains the following properties:

{
    // `id` is the primary key for indexing objects - always a number
    id: 'string',
    source_name: 'string', // publisher
    author: 'string',
    title: 'string',
    description: 'string', // small excerpt describing the blog
    url: 'string',
    image: 'string', // URL to cover image
    published: 'string', // ISO datetime (2019-09-03T13:00:07Z)

    // number: year of publication extracted from `published` property
    publication_year: 2019, 
    content: 'string' // short excerpt from blog
}

Creating the blogs index

To create a blogs index, we’ll upload our blogs.json data to this index so we can later search or modify this data.

To interact with a MeiliSearch instance, we have to require the meilisearch package at the top of our index.js file:

const MeiliSearch = require('meilisearch')

Now, we’ll use a main function that allows us to use the async/await syntax. We’ll use the main function throughout this tutorial to update code snippets.

Before we can interact with the MeiliSearch instance, we need to establish a connection.

const main = async () => {
    const client = new MeiliSearch({
        host: 'https://sandbox-pool-bwwv53a-3bsbgmeayb75w.ovh-fr-2.platformsh.site',
        headers: {
            'X-Meili-API-Key': 'your-master-key'
        }
    })
}

main()

Let’s create an index. The client object exposes all methods for interacting with the API of our MeiliSearch instance.

const main = async () => {
    const client = new MeiliSearch({
        host: 'https://sandbox-pool-bwwv53a-3bsbgmeayb75w.ovh-fr-2.platformsh.site',
        headers: {
            'X-Meili-API-Key': 'your-master-key'
        }
    })

    await client.createIndex('blogs')
}

main()

To create the index, we have to execute the index.js file:

node index.js

For simplicity’s sake, we won’t repeat all the code.

Now, let’s list all indexes to verify whether we successfully created the blogs index.

const indexes = await client.listIndexes()
console.log(indexes)
/* Output:
[
    {
        name: 'blogs',
        uid: 'blogs',
        createdAt: '2020-12-04T17:27:43.446411126Z',
        updatedAt: '2020-12-04T17:51:52.758550969Z',
        primaryKey: null
    }
]
*/

MeiliSearch has yet to set a primary key for the prizes index. When we add data in the next step, MeiliSearch will automatically pick the primary key since our data set contains an id field.

Do you see the blogs index? Let’s move on!

Uploading a dataset in MeiliSearch

The quickest way to upload a large dataset to your MeiliSearch instance is by using a tool such as cURL. Be sure to execute this command in the directory that contains the blogs.json dataset. Pay attention to upload the data to the right index: /indexes/blogs/. Again, add your master key if you’ve configured this.

curl -i -X POST 'https://meilisearch-sandbox.site/indexes/blogs/documents' \
    --header 'content-type: application/json' \
    --header 'X-Meili-API-Key: your-master-key' \
    --data-binary @blogs.json

To verify whether our data has been uploaded successfully, let’s list our indexes again. This time, the primary key field should list the id property.

node index.js

Is the primary key set? The next step explains how to add additional documents.

Adding documents using the MeiliSearch JavaScript client

What about adding a new document to our blogs.json dataset? Here’s how to add additional documents to your MeiliSearch instance.

Before we can add a document, let’s define a new fictive blog created by ABC News. Note that we define an array of documents. By default, we can add multiple documents at once.

const documents = [
    {
        id: '201',
        source_name: 'ABC News',
        author: 'Gregorian',
        title:
            '\'This Tender Land\' is an affecting story about growing up',
        description:
            '"This Tender Land" by William Kent Krueger is an affecting story about growing up and overcoming a childhood filled with neglect, abuse and racism during the Depression.',
        url:
            'https://abcnews.go.com/Entertainment/wireStory/tender-land-affecting-story-growing-65359757',
        image: '',
        published: '2019-09-03T15:56:49Z',
        content:
            '"This Tender Land: a Novel" (Atria Books), by William Kent Krueger\r\nStrands of the adventures of Huck Finn and Tom Sawyer on the Mississippi River echo throughout William Kent Krueger\'s lyrical, compassionate "This Tender Land" in which four children try to e… [+2822 chars]'
    }
]

Next, we need to retrieve our blogs index and call the addDocuments method. This method accepts our documents array.

const index = client.getIndex('blogs')
let response = await index.addDocuments(documents)
console.log(response) // => { "updateId": 0 }

The addDocuments function returns a JSON object with the updateId property when the documents have been added successfully. The updateId for newly created documents is set to 0. Every time we make changes to a particular document, the updateId increases. This system is very similar to an incremental versioning system to track changes.

In the next step, we’ll use the search method to retrieve our newly added document.

Searching for documents in MeiliSearch

In the previous step, we’ve added a new document with author Gregorian. Let’s try to query for this document. We can use the search method for this, which is exposed by the index object.

const index = client.getIndex('blogs')
const search = await index.search('Gregorian')
console.log(search)

/* Output:
{
    hits:
        [{
            id: '201',
            source_name: 'ABC News',
            author: 'Gregorian',
            ...
        }],
    offset: 0,
    limit: 20,
    nbHits: 1,
    exhaustiveNbHits: false,
    processingTimeMs: 4,
    query: 'Gregorian'
}
*/

The returned result contains several properties:

  • hits contains the items that match the search intent
  • nbHits represents the number of matching items
  • processingTimeMs represents the time in milliseconds to retrieve the search result
  • query is the query we sent to our MeiliSearch instance

Four milliseconds — that’s quick!

How to modify documents with MeiliSearch

Now let’s update the blog created by our author Gregorian. And let’s say that, actually, we made a mistake: the blog was published by not ABC News but Fox News.

Updating a document with MeiliSearch is straightforward. We can use the original object and change some of its values. Since MeiliSearch has automatically assigned the primary key to the id field, we need to send the id field with our request. On top of that, we use the same addDocuments function for updating documents.

Here’s how you can do this.

const original = { "id": "200", "source_name": "ABC News", "author": "The Associated Press", "title": "Sheryl Crow feels 'liberated' by saying goodbye to the album", "description": "Rocker Sheryl Crow says she feels liberated by saying goodbye to the full-length album format, but goes out with a star-studded bang on \"Threads.\"", "url": "https://abcnews.go.com/Entertainment/wireStory/sheryl-crow-feels-liberated-goodbye-album-65358836", "image": "https://s.abcnews.com/images/Entertainment/WireAP_e56806824cfe4f4aa287b73b4b2fcaaf_16x9_992.jpg", "published": "2019-09-03T15:27:46Z", "publication_year": 2019, "content": "Sheryl Crow has a lifetime of stories of hanging out with rock stars, pop stars, athletes, icons and music royalty, some even featured on her new collaborative record, but don't expect her to start revealing any secrets in an autobiography.\r\n\"I mean, there ar… [+4022 chars]" }

const updatedDocument = {
    ...original,
    source_name: 'Fox News'
}

const index = client.getIndex('blogs')
const updatedResponse = await client.getIndex('blogs').addDocuments([updatedDocument])
console.log(updatedResponse) // => { "updateId": 1 }

Note that the updateId property increased by one since we modified the document.

How to modify MeiliSearch ranking rules

Let’s take a look at a more advanced setting in MeiliSearch: ranking rules. According to the official docs:

Ranking rules are built-in rules that ensure relevancy in search results. MeiliSearch applies ranking rules in a default order which can be changed in the settings. You can add or remove rules and change their order of importance.

By default, MeiliSearch employs the following order for ranking rules:

1. typo
2. words
3. proximity
4. attribute
5. wordsPosition
6. exactness

We can access the ranking rules via the getSettings method:

const index = client.getIndex('blogs')
const settings = await index.getSettings()
console.log(settings)

/* Output:
{
 rankingRules:
   [ 'typo',
     'words',
     'proximity',
     'attribute',
     'wordsPosition',
     'exactness' ],
  distinctAttribute: null,
  searchableAttributes: [ '*' ],
  displayedAttributes: [ '*' ],
  stopWords: [],
  synonyms: {},
  attributesForFaceting: []
}
*/

For this example, let’s take a look at wordsPosition and exactness. You can find an explanation for all ranking rules in the MeiliSearch documentation.

Changing the ranking rules order to wordsPosition

We need to change the settings for our blogs index to prioritize wordsPosition and move exactness to the back.

Words Position: Results are sorted by the position of the query words in the attributes: find documents that contain query terms earlier in their attributes first.

We only have to pass the property we want to change to the updateSettings function. Therefore, we pass the rankingRules property.

const index = client.getIndex('blogs')
await index.updateSettings({
    rankingRules:
        [
            "wordsPosition",
            "typo", 
            "words", 
            "proximity", 
            "attribute",
            "exactness"
        ]
})

To see the effect of the ranking rules, let’s query for the word cent. Let’s limit our search to five results. Furthermore, let’s only print the content of the article.

const index = client.getIndex('blogs')
const search = await index.search('cent', { limit: 5 })
search.hits.map(data => console.log(data.content, '\n\n'))

This word appears in the following ways:

  • center (x2)
  • central
  • Century
  • center-left

We can also see the effect of the wordsPosition ranking rule by looking at the position of the word cent in the text. The word cent appears earlier in the text for the first results.

Walking through the gym of a local community outreach **center** in Choloma, Honduras,


Walking through the gym of a local community outreach **center** in Choloma, Honduras,


The Latest on a boat capsizing along the White River in **central** Indiana (all times local):


Jim Henson has one of the most storied legacies of any 20th **Century** creative.


ROME (Reuters) - Members of the anti-establishment 5-Star Movement backed a proposed coalition with the **center-left** Democratic Party (PD) on Tuesday

Changing the ranking rules order to "exactness"

Now, let’s change swap ranking rules wordsPosition and exactness to see the differences.

This time, we need to change the settings for our blogs index to prioritize exactness over wordsPosition.

Exactness: Results are sorted by the similarity of the matched words with the query words: find documents that contain exactly the same terms as the ones queried first.

Again, let’s update the rankingRules property:

const index = client.getIndex('blogs')
await index.updateSettings({
    rankingRules:
        [
            "exactness",
            "typo", 
            "words", 
            "proximity", 
            "attribute",
            "wordsPosition"
        ]
})

To see the effect of the ranking rules, let’s query for the word cent again:

const index = client.getIndex('blogs')
const search = await index.search('cent', { limit: 5 })
search.hits.map(data => console.log(data.content, '\n\n'))

This word appears in the following ways:

  • cent -> 5.2 per cent matches first as our ranking rules prioritize exact word matches
  • center-left
  • center (x2)
  • central

That’s it!

Conclusion

In this MeiliSearch tutorial, we demonstrated how to add, update, and query for documents using MeiliSearch. On top of that, we’ve introduced you to the concept of ranking rules.

There’s so much more to explore. For instance, you can define stop words to improve your search, set synonyms, or add facet filters to index your data.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Michiel Mulders Michiel loves the NodeJS and Golang programming languages. A backend/core blockchain developer and avid writer, he's very passionate about blockchain technology.

Leave a Reply