Search Eloquent Relationships with Laravel Scout and Meilisearch

Dan Pastori avatar
Dan Pastori March 28th, 2022

When I first started to use Meilisearch with Laravel Scout, I wanted to query an Eloquent relationship along with my search. Like everything with Laravel, there was already a solution available. In this tutorial we will expand on the Laravel documentation and provide a few examples of how to include an Eloquent relationship’s model with a Meilsearch indexed record.

Prerequisite

I’m going to assume you already have a properly functioning Meilisearch instance up and running. Along with that, I’m going to assume you are using Laravel Scout with Laravel 9. However, this will work with older versions of Laravel if needed.

This is a really quick article, but is something I implement on most of my Meilisearch instances. The ability to search an index and get important related fields will make the UX of your app so much nicer!

Why would you do this?

Say you have an index of customers. Each of those customers has multiple email addresses. The email addresses are located in a related table in your database, say customer_email_addresses. When you search for customers with Laravel Scout in your app, allowing the user to search by an email address is crucial. If the email addresses are in a different table, no results would be returned. Luckily, there’s a simple solution to include these relationships in your Meilisearch index.

Step 1: Find Eloquent Model to Update

The first step is to find the model you want to query a relationship with. Let’s use our customer example. Say you have Meilisearch already configured on your Customer Model with a proper relationship to have many email addresses:

<?php

namespace App\Models\Customers;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Customer extends Model
{
    use Searchable;

    protected $table = 'customers';

    public function emailAddresses()
    {
        return $this->hasMany('App\Models\CustomerEmailAddress', 'customer_id', 'id');
    }
}

When Meilisearch indexes this record, the email addresses will not be included. Luckily Laravel has a solution. When you use the Searchable trait you can overwrite the method toSearchableArray() on your model. Whenever your model is updated, this method will be called and whatever is returned will be indexed by Meilisearch.

Step 2: Implement to Searchable Array or Make Searchable Using

First, let’s overwrite the toSearchableArray() on our model by adding the following:

public function toSearchableArray()
{
    $customer = $this->with('emailAddresses')
                     ->where('id', '=', $this->id)
                     ->first()
                     ->toArray();

    return $customer;
}

What this method does is allow you to define what gets indexed and becomes searchable when your model is indexed. There are a few things to note.

First, the secret to the method is the ->with('emailAddresses') relationship. This is what loads our related email addresses to be indexed. Now whenever Laravel Scout sends the model to be indexed, the email addresses will get passed along and become searchable.

Second, we have to return an array. That’s why we append the ->toArray() at the end of our query builder.

The other option, you could also add the method makeAllSearchableUsing( $query ) to your model. This will allow you to add relationships to your query before they are imported. In our example, this method would look like:

protected function makeAllSearchableUsing($query)
{
    return $query->with('emailAddresses');
}

I’d have to say this is the better option. However, I wanted to show both for the reasons of sensitive data. If you override the toSearchableArray() you can strip out any sensitive data you might not want. If you don’t have to worry about sensitive data on your models, then the makeAllSearchableUsing() method will work great!

That’s really all there is to it. Let’s talk a little bit about sensitive data.

Warning: Do NOT Index Sensitive Data

This is REALLY easy to do if you don’t look out for it. First, a word to the wise, any sensitive data that requires extreme security should be encrypted in the database to begin with. However, there could be other data that you don’t want index or returned with search results. Even such things as addresses.

You will have to remove these fields before your return statement at the end of your method. Another solution is to only select the fields that you want searchable. With massive datasets, this is also preferred so you can scope down only what is important to search. Here’s an example on how to do that with your customer model:

public function toSearchableArray()
{
    $customer = $this->only(['id', 'first_name', 'last_name'])
                     ->with('emailAddresses')
                     ->where('id', '=', $this->id)
                     ->first()
                     ->toArray();

    return $customer;
}

The ->only() method accepts an array of fields you wish to return from your query. Quick note, make sure you include the id field! This is your primary key, also the reference that your foreign key will need to set up a proper relationship.

Optional: Re-index Records

If you already have records in your index, make sure that you re-index with Meilisearch. There are a few ways to do that.

If you don’t have a ton of data, you could flush and re-build the entire index using the following commands:

// Clear the index
php artisan scout:flush "App\Models\Customer"

// Re-populate the index
php artisan scout:import "App\Models\Customer"

This most likely wouldn’t be the best scenario if your app is already in production. You can “upsert” your models by running:

$customers->searchable();

You will have to build a migration for this or some way to trigger this method.

Conclusion

I find the overwriting of the toSearchableArray() method extremely useful when building a solid search experience. Hopefully this will help you to add an excellent search experience to your app!

Interested in learning more about building APIs with Laravel 9? Check out our book, “The Ultimate Guide to Building APIs and Single-Page Applications”.

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.