Advanced Meilisearch Queries with Laravel Scout

Dan Pastori avatar
Dan Pastori April 18th, 2022

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 querymethod 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!

Support future content

The Ultimate Guide to Building APIs and Single-Page Applications with Laravel + VueJS + Capacitor book cover.

Psst... any earnings that we make off of our book is being reinvested to bringing you more content. If you like what you read, consider getting our book or get sweet perks by becoming a sponsor.

Written By Dan

Dan Pastori avatar Dan Pastori

Builder, creator, and maker. Dan Pastori is a Laravel certified developer with over 10 years experience in full stack development. When you aren't finding Dan exploring new techniques in programming, catch him at the beach or hiking in the National Parks.

Like this? Subscribe

We're privacy advocates. We will never spam you and we only want to send you emails that you actually want to receive. One-click unsubscribes are instantly honored.