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.
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:
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.
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
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 }
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!
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.
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.
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 intentnbHits
represents the number of matching itemsprocessingTimeMs
represents the time in milliseconds to retrieve the search resultquery
is the query we sent to our MeiliSearch instanceFour milliseconds — that’s quick!
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.
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.
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
"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 matchescenter-left
center
(x2)central
That’s it!
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.