The art of sorting
If you haven't heard it yet, Meilisearch v0.22s out, and it comes with a highly requested new feature: sorting at search time.
Search-time sorting is when results are sorted according to parameters decided at query time. By default, Meilisearch orders results according to their relevancy, but you can configure it to let users decide at search time what results they want to see first.
Let me show you how powerful this feature is. If I wanted to search for a MacBook and sort the results by ascending price, I would send the following query:
curl -X POST 'http://localhost:7700/indexes/computers/search' --data '{ "q": "MacBook", "sort": [ "price:asc" ] }'
I could do the opposite and search by descending price. I would just need to replace asc
by desc
in my request.
The old way
Before Meilisearch v0.22, you could only sort search results using the custom ranking rules.
Thanks to the custom ranking rules, we could have our search results sorted by the numeric attribute of our choice. However, the sorting was not at search time, the sorting order was decided in our index settings.
To change the sort order of the search results in the user interface, we had to duplicate the index and set different custom ranking rules for each index.
That's what I did for our MoMA demo a few months ago. For those who missed it, the MoMA demo is a Meilisearch demo created with the artworks dataset of the Museum of Modern Art available in their GitHub repository. You can learn more about the demo in this blog post wanted the users to be able to sort the artworks by date, so I had to create three indexes:
- artWorks
with the default built-in ranking rules
- artWorksAsc
with a custom ranking rule for ascending sort
- artWorksDesc
with a custom ranking rule for descending sort
// Get or create indexes const artWorksIndex = await client.getOrCreateIndex('artWorks', { primaryKey: 'ObjectID' }) const artWorksAscIndex = await client.getOrCreateIndex('artWorksAsc', { primaryKey: 'ObjectID' }) const artWorksDescIndex = await client.getOrCreateIndex('artWorksDesc', { primaryKey: 'ObjectID' }) const defaultRankingRules = [ 'typo', 'words', 'proximity', 'attribute', 'exactness' ] const rankingRulesAsc = [ 'typo', 'words', 'proximity', 'attribute', 'exactness', 'asc(DateToSortBy)' // ascending sort ] const rankingRulesDesc = [ 'typo', 'words', 'proximity', 'attribute', 'exactness', 'desc(DateToSortBy)' // descending sort ]
The duplication, or triplication, gave the impression of sorting at search time as Meilisearch is blazingly fast.
At that time, sorting was on our roadmap, in the Under Consideration tab, and it was one of the most voted features (86 votes).
We have a public roadmap where you can submit feature or integration ideas and vote for the existing ones.
With the new v0.22 sort feature
Today, it's a reality. And I've updated the demo to integrate this feature. Let's go through what has changed.
Behind the scenes
First of all, I have added the following line to the settings:
sortableAttributes: ['DateToSortBy']
This addition is necessary because we need to inform Meilisearch of the attributes we want to use for sorting.
We only need one index (not three) and the default ranking rules. This has considerably reduced the back-end code. You can change the position of the sort
rule to tune the relevancy of the search results according to your needs. By default, the sort
rule in 5th position of the ranking rules to promote results relevant to the user's search.
You can learn more about the ranking rules and how they affect relevancy here.
And if you are curious about why the sort
rule is in 5th position by default, you can check this GitHub issue where our Product Manager explains this choice.
With these modifications, the big block of code above has been reduced to the following line:
// Get or create the index const index = await client.getOrCreateIndex('artWorks', { primaryKey: 'ObjectID' })
Easy, right? But, what about the front end?
In the spotlight
For this demo I used Vue InstantSearch, combined with Instant Meilisearch. This connects the Meilisearch instance with the open-source InstantSearch front-end tools allowing us to customize the search environment effortlessly.
The previous code looked like this:
<ais-sort-by :items="[ { value: 'artWorks', label: 'Featured' }, { value: 'artWorksAsc', label: 'Date asc.' }, { value: 'artWorksDesc', label: 'Date desc.' } ]" :class-names="{ 'ais-SortBy': 'MyCustomSortBy' }" />
I just needed to transform it into this:
<ais-sort-by :items="[ { value: 'artWorks', label: 'Featured' }, { value: 'artWorks:DateToSortBy:asc', label: 'Date asc.' }, { value: 'artWorks:DateToSortBy:desc', label: 'Date desc.' } ]" :class-names="{ 'ais-SortBy': 'MyCustomSortBy' }" />
As you can see, instead of using 3 different indexes (artWorks
, artWorksAsc
, and artworksDesc
), we only need to append the attribute's name to the artWorks
index followed by the desired sort order, asc
or desc
.
'artWorks:DateToSortBy:asc' 'artWorks:DateToSortBy:desc'
And this is it. Smooth and simple. You can test it here.
Web interface with the query "Magritte" sorting by ascending and then descending date gif
The end-user may not notice the difference, but resource-wise it's much more efficient.
Another great advantage of v0.22 is the new indexer. It's way faster than before, so if you're still using an older version of Meilisearch, I strongly encourage you to upgrade it.
The demo source code is available on GitHub. I invite you to play with it, you can add other attributes to sort by, as long as they are strings or numeric values. For a more in-depth explanation of sorting, take a look at the dedicated section of the documentation.
Picture by Héctor J. Rivas on Unsplash