Advanced Meilisearch Queries with Laravel Scout
We’ve touched on a few configurations we can update with Meilisearch to make it more user friendly and powerful. A few that we worked on were setting sortable and filterable attributes on your models. Let’s open the gates and show how to use all of the power features of Meilisearch with Laravel Scout!
These advanced queries give you full access to the power that Meilisearch provides. You can fine tune your app and make amazing search experiences for your users. Since these are extremely custom to your application, I’ll simply show you how to build these advanced queries and you can run wild!
Prerequisite
I’m assuming you have Laravel Scout installed and connected to a Meilisearch instance. Other than that, you are ready to go!
Why use these advanced queries?
When optimizing data for filtering, searching, and sorting, the more optimization the better. Meilisearch provides advanced filtering (when configured, see Filtering with Meilisearch and Laravel Scout), geo queries, etc. Laravel Scout supports some of this fluently, but when you really want to optimize, you will have to interact directly with Meilisearch.
One aspect I absolutely love about Laravel is that you have the capability to add these customizations. Yea, it’s complicated to understand what you need to achieve sometimes, but Laravel allows you to do that with ease.
Designing your Advanced Query with Meilisearch
Let’s say you have a massive customer database with millions of records that’s searchable, filterable, order-able, etc. You really want to give the power to the end user to search this data and apply all sorts of filters. Some of these filters could be wild, such as “distance from your headquarters to the customer location” or “customers between the ages of 22 and 43 and have been added in the last 10 days”. You want to relate these to an Eloquent model and use Scout natively, so how do you do it?
Well, you can pass a call back function to the search()
method inherited on the model by the Searchable
trait. Sound like a lot? Let’s check it out:
$filters = Request::get('filters');
$customers = Customer::search(
$term,
function( Indexes $meiliSearch, string $query, array $options ) use ( $filters ){
$options['sort'] = [$filters['sort'].':'.$filters['sort_direction']];
$options['filter'] = 'created_at > '.$filter['created_after'].' AND company_id = "'.$filter['company_id'].'"';
return $meiliSearch->search( $query, $options );
}
)->get();
Make sure that you add use MeiliSearch\Endpoints\Indexes;
to the top of your class. This way you can continue to use the advanced features of Meilisearch!
Breaking Down The Advanced Meilisearch Callback
Let’s break this down. First, we grab all of the filters configured by the user through the request:
$filters = Request::get('filters');
Next, we start to build the customer query. Right away, it should look relatively familiar. You call the search
method on the Customer
model and pass it a search term. However, the call back function is where things level up a little bit. Let’s break that down by itself.
The first parameter of the call back function is Indexes $meilisearch
. This is the actual connection interface to the Meilisearch instance. Here you have direct access through the PHP library to Meilisearch itself. Kind of the key ingredient to doing advanced queries.
The second parameter is the string $query
. The $query
parameter contains the string the user wants to query (essentially the $term
variable). You will then pass this along with your overrides to Meilisearch.
Finally, you have the array $options
parameter. This parameter is what contains the options you can pass to Meilisearch to perform advanced queries. Pretty much anything in the advanced section of the Meilisearch docs can be used here!
If you look inside the callback function’s body, you see that we set the sort
key and the filter
key on the $options
array. This is where we can construct our complex queries and dynamic searches. Just make sure you have your fields configured to be sortable and filterable within Meilisearch! One thing to note, is the sort key expects an array even if you are just sorting by one attribute. It should look like:
$options['sort'] = [$filters['sort'].':'.$filters['sort_direction']];
Since we are overriding query and injecting our own sorting, advanced filters, etc. we need to return the Meilisearch index and call the ->search()
method. In this method, we need to pass in the $query
and the $options
variables. This would be the same if you were using the Meilisearch PHP package itself, outside of Laravel Scout. However, with this approach, we can have the best of both worlds. The power of the PHP package and the fluent approach of Laravel Scout binding to our Eloquent models.
Before we move to the next section, we add a use
statement to the end of the function to ensure our request variables can be accessed from within the callback function. Just a heads up!
What else can you do?
Within this advanced function, you can do complex filters, geo filters, faceted searches, etc. Here are the links to the documentation in Meilisearch for some inspiration:
A lot of features not supported fluently through Eloquent’s query builder, but can be used none the less! This power can really help to ensure your users have an excellent search experience.
Loading Relationships After Searching
One of the other pieces of of functionality I’d like to touch on and give an example of is how to load relationships after a search has been made. This happens after you search your Meilisearch instance, similar to if you are doing an Eloquent query. It’s documented well in the Laravel docs, but it took me awhile to grasp it. So maybe another approach will help you as well.
Normally, in Eloquent if you’d like to load a relationship you’d add a ->with('invoices')
or something similar to the query builder. You’d have something like this:
$customers = Customer::where('name', 'LIKE', 'Dan%')->with('invoices')->get();
However, when building a search request to Meilisearch, this won’t work. You have to run a ->query()
method and pass it a callback function. As the documentation states, “this callback is invoked after the relevant models have already been retrieved from your application’s search engine, the query
method should not be used for “filtering” results.” What that means is don’t put a where
in this function, only use it for adding relationships.
So let’s take our simple Eloquent query above and convert it to query Meilisearch instead:
$customers = Customer::search('Dan')
->query( function( $query ){
$query->with('invoices');
} )->get();
Now we will search Meilisearch, get all of the results that match “Dan” and load any invoices the customer may have associated with them.
Conclusion
By using some of these advanced features of Meilisearch combined with the fluent nature of Laravel Scout, you can create a super powerful search engine. Your user’s experience will benefit dramatically! You can have the best of both worlds with a high speed, full text search powered by Meilisearch. And you can use the beauty of an Eloquent query to relate and return data.
Implementing Meilisearch is beneficial in any application, especially with a high availability API. If you want to learn more about building API Driven Applications, check out our book. We are updating it for Laravel 9 and Vue 3!
If you have any questions, feel free to reach out on Twitter or in our community forum!