MeiliSearch & Lando: Test Driving an Alternate Search Backend
What is MeiliSearch?
MeiliSearch is an open source search engine written in rust that positions itself as a very good, low configuration search backend for search-as-you-type experiences. In particular, MeiliSearch enables a really nice experience out of the box in terms of typo tolerance, speed, and relevancy. If this sounds familiar, it's likely because it was inspired by the search experience that Algolia provides out of the box. Algolia brings a lot to the table as a product, including being a managed service, providing a nice frontend to manage search configuration, and some really powerful personalization tools. That said, having an open source alternative that is easy to configure while still providing a nice search experience is really enticing.
Why did we choose it?
In fact, this entire article is born of needing an easy to configure search backend while waiting on a client to purchase an Algolia service. The main goal of the project was to provide a new tool for the client to process and index content from multiple sources into Algolia. Having built the tool in Laravel, we were able to make use of Laravel Scout as an abstraction on the search backend so it was really easy to decouple our logic from the Algolia service. That also afforded us the opportunity to figure out an alternative way to test the indexing process. Algolia's free tier only allows indexing up to 10,000 records. For a client that has far more records than that (~1 million), we didn't want to be held back by that limit while waiting on the purchase order. So we turned to MeiliSearch.
As an alternative, it fell into our laps mainly because it is open source and already has a Laravel Scout driver. This meant we wouldn't have to write any code specific to MeiliSearch, we'd only need to get it running on our local environments. And since it comes out of the box with an example search frontend, we could also easily preview indexed documents and data.
While we haven't tried MeiliSearch in a production environment, it has been absolutely worth it to get it running locally and seeing how good it can be. The least intuitive part of the process was getting it set up with Lando, our dev tool of choice in the Adapt USA office to manage local development environments. So let's look at how that can be done.
Lando, in short, is a nodejs wrapper around docker-compose. It has quite a few pre-configured recipes for basic services and for this project we are using the Laravel recipe. But what happens when Lando doesn't support a service out of the box? It turns out that one option is to define a service in the
.lando.yml file that is essentially a passthrough docker-compose configuration. In the case of MeiliSearch, your search service will be defined as the following:
If you're familiar with Lando or Docker you may notice a couple things in that definition:
- Lando hijacks a Docker containers entrypoint. So if your container defines a custom entrypoint, it needs to be the first argument in the
command. In this case, we had to include the custom entrypoint
- In this definition, the exposed port is fixed to
7700. This is not required as Lando can manage how that port is exposed. You could set the value to
This will expose port 7700 externally on a non-specified open port. This is fine, but
- it means that the port is subject to change anytime you restart the container.
- It also means that to find the exposed port you have to run
docker ps. This is because it doesn’t appear that Lando exposes the internal and external connections of custom compose services when you run
So I’ve explicitly set the port in this case.
Laravel scout configuration
I'm skipping over quite a bit of Laravel setup here. The assumption is that you already have an application and a model that you want to make searchable, in which case there’s not a whole lot to do, but it's worth finishing off the configuration to see how it all connects.
Set the following values in
MEILISEARCH_HOST value is Lando’s internal connection to the
meilisearch service. This will be used when posting or fetching data on the backend.
Additions to your model
Searchable trait to your model.
Optionally, you may want to define a
toSearchableArray method. Laravel has a default
toSearchableArray method that will convert the model into an array, but if you define your own, you can output specific data. For example, if you have some data in a json column, it may make sense to do something like the following:
Using artisan, you can index data with the command
lando php artisan scout:index App\\\\Models\\\\YourSearchableModelName .
As mentioned before, MeiliSearch comes with a nice example front end. It will be exposed at http://localhost:7700 (or whatever port you defined).
- To repeat my note from above, MeiliSearch requires documents to have a primary key. The default primary key field is
idit must be included when indexing data or the indexing action will fail. The key also must “be of type integer or string only composed of alphanumeric characters, hyphens (-) and underscores”.
- MeiliSearch processes indexing operations asynchronously. So when you post to the api endpoint, you get back an updateId. This is great, but it can make it difficult to understand from the api response if your update processed correctly. For this, MeiliSearch exposes an api endpoint to check update statuses. You can also just hit this in the browser at http://127.0.0.1:7700/indexes/[index_id]/updates to view all update statuses or http://127.0.0.1:7700/indexes/[index_id]/updates/[update_id] to view the status of a particular update id.
If you would like to know more about Meilisearch and how it compares to other search tools, it's probably best for to hear it from them. You can find comparisons to Elasticsearch and Algolia, among others, on their site.
That said, it felt like an impressive tool that offers a lot of power without a lot of work. It's definitely something that we'll be keeping an eye on.