M. Niyazi Alpay
M. Niyazi Alpay
M. Niyazi Alpay

I've been interested in computer systems since a very young age, and I've been programming since 2005. I have knowledge in PHP, MySQL, Python, MongoDB, and Linux.

 

about.me/Cryptograph

  • admin@niyazi.org
Meilisearch and Laravel Integration

In this article, I will talk about MeiliSearch and Laravel Integration.

MeiliSearch is a search engine that operates on the same logic as Elasticsearch. However, Elasticsearch consumes a lot of resources. MeiliSearch, on the other hand, can perform similar tasks with fewer resources. Now, I don't want to be misunderstood here. MeiliSearch should not be understood as better than Elasticsearch. Elasticsearch can be set up with a distributed server structure and can be better in terms of features and capacity than MeiliSearch. However, if you don't want to consume too many resources, MeiliSearch is a RESTful search API that requires fewer resources to perform relevant searches. In short, we can say MeiliSearch is "Elasticsearch for the poor."

In fact, we can say that Meilisearch is a tool that allows us to create an on-site search engine. Years ago, I directly talked about search without using MySQL by making a "site internal search engine" with a like query. Here we will perform a more advanced search process.

MeiliSearch Installation

I will explain the installation process on Ubuntu. You can perform the installation steps by running the following commands in the terminal.

apt update
apt install curl -y
curl -L https://install.meilisearch.com | sh
chmod +x meilisearch
mv ./meilisearch /usr/local/bin/

 

useradd -d /var/lib/meilisearch -b /bin/false -m -r meilisearch
curl https://raw.githubusercontent.com/meilisearch/meilisearch/latest/config.toml > /etc/meilisearch.toml
mkdir /var/lib/meilisearch/data /var/lib/meilisearch/dumps /var/lib/meilisearch/snapshots
chown -R meilisearch:meilisearch /var/lib/meilisearch
chmod 750 /var/lib/meilisearch

 

cat << EOF > /etc/systemd/system/meilisearch.service
[Unit]
Description=Meilisearch
After=systemd-user-sessions.service

[Service]
Type=simple
WorkingDirectory=/var/lib/meilisearch
ExecStart=/usr/local/bin/meilisearch --config-file-path /etc/meilisearch.toml
User=meilisearch
Group=meilisearch

[Install]
WantedBy=multi-user.target
EOF

 

After these steps, you need to open the /etc/meilisearch.toml file and make the following definitions.

env = "development"
master_key = "YOUR_MASTER_KEY "
db_path = "/var/lib/meilisearch/data"
dump_dir = "/var/lib/meilisearch/dumps"
snapshot_dir = "/var/lib/meilisearch/snapshots"

Meilisearch installation is now complete. You can set the service to start at boot with the "systemctl enable meilisearch" command. You can start the service with the "systemctl start meilisearch" command.

If you want to install an SSL certificate for this service, you will also need to edit the following definitions in the /etc/meilisearch.toml file.

http_addr = "localhost:7700"
ssl_cert_path = "./path/to/certfile"
ssl_key_path = "./path/to/private-key"

Meilisearch is now ready, let's move on to integrating with Laravel.

First, we install the Scout package. Scout is a package that allows full-text search in the Laravel model.

composer require laravel/scout
php artisan vendor:publish --provider="LaravelScoutScoutServiceProvider"

We will use Meilisearch as the driver for Scout, so we need to install the meilisearch/meilisearch-php package.

composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle

Add the following to the .env file

SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=masterKey

If you are installing this on a system with existing data, you should run the following command to synchronize the contents with Meilisearch.

php artisan scout:import "AppModelsModelName"

You can run the following command to delete all indexes on the Meilisearch side.

php artisan scout:flush "AppModelsModelName"

We need to call the "LaravelScoutSearchable" class on the model we want to search. Calling the Searchable class is sufficient for all operations. Here, every insert, update, and delete operation in the database is synchronized with Meilisearch.

Let's continue with examples.

Assume that our database structure is as follows:

  • categories
    • id -> primary_key
    • category_name -> varchar
  • products
    • id -> primary_key
    • product_name  -> varchar
    • product_description -> varchar
  • product_categories
    • id -> primary_key
    • product_id -> index
    • category_id -> index
  • attributes
    • id -> primary_key
    • attribute_name -> varchar
  • product_attributes
    • id -> primary_key
    • product_id -> index
    • attribute_value -> varchar

Let's assume our Products model is as follows. 

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Products extends Model
{
    use HasFactory;
    use Searchable;

    protected $table = 'products';

    protected $fillable = [
        'product_name',
        'product_description',
        'user_id'
    ];

    public function user()
    {
        return $this->belongsTo(User::class, 'user_id', 'id');
    }

    public function productAttributes()
    {
        return $this->hasMany(ProductAttributes::class, 'product_id', 'id');
    }

    public function productCategories()
    {
        return $this->hasMany(ProductCategories::class, 'product_id', 'id');
    }

    public function toSearchableArray()
    {
        $array = $this->toArray();
        $array['user'] = $this->user->name;

        $array['product_attributes'] = $this->productAttributes->map(function ($data) {
            return [
                'attribute_name' => $data->attribute->attribute_name,
                'attribute_value' => $data->attribute_value,
            ];
        })->toArray();

        $array['product_categories'] = $this->productCategories->pluck('category.category_name')->toArray();
        return $array;
    }
}

We determine what will be sent to Meilisearch in the toSearchableArray function. This will allow us to search for the same product based on its feature, category, or other product information while searching for products on the site. The logic here is as follows: we entered all the information related to the product into separate tables in the database and linked the models by binding them, and sent all the added content to Meilisearch. An entry was created for all product attributes and categories on the Meilisearch side for a product. When we make a query on the site, this query is executed on the Meilisearch side, and the ID values of the results are returned to us. Laravel queries these ID values directly in the database with the "where in" condition and brings all the results. Thus, we can perform the search operation quickly without searching in all tables on the database side.

Now let's move on to how we perform the search operation on the controller side.

<?php

namespace App\Http\Controllers;

use App\Models\Products;
use Illuminate\Http\Request;

class ProductsController extends Controller
{
    public function products(Request $request, Products $products)
    {
        $p = $products->search($request->search)->query(function ($query){
            $query->with(['productAttributes', 'productAttributes.attribute', 'productCategories.category', 'user']);
        })->paginate(10);
        echo "<pre>";
        print_r($p);
        echo "</pre>";
    }
}

It is sufficient to call the model only with search(). If $request->search comes empty, it will bring all results.

We can add where conditions and perform sorting here as well. However, we need to determine the filterable, searchable, and sortable fields in the config/scout.php file in the Meilisearch index.

'meilisearch' => [
        'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
        'key' => env('MEILISEARCH_KEY'),
        'index-settings' => [
            AppModelsProducts::class => [
                'filterableAttributes'=> [
                    'id',
                    'product_name',
                    'product_description',
                    'user_id',
                    'user',
                    'product_attributes',
                    'product_categories'
                ],

                'searchableAttributes' => [
                    'id',
                    'product_name',
                    'product_description',
                    'user_id',
                    'user',
                    'product_attributes',
                    'product_categories'
                ],

                'sortableAttributes' => ['product_name', 'created_at', 'updated_at'],
            ],
        ],
    ],

We can revise the query in our controller.

$p = $products->search($request->search)->query(function ($query){
            $query->with(['productAttributes', 'productAttributes.attribute', 'productCategories.category', 'user']);
        })->where('user_id', $request->user_id)->orderBy('created_at', 'DESC')->paginate(10);

Here, we can send the category of the product, its attributes, title, or the name of the user who added the product as the search value with $request->search. 

This image nicely summarizes the query relationship between Laravel - Meilisearch and the database.

You can also access example project files at https://github.com/niyazialpay/LaravelMeilisearchExample.

Muhammed Niyazi ALPAY - Cryptograph

Senior Software Developer & Senior Linux System Administrator

Meraklı

PHP MySQL MongoDB Python Linux Cyber Security

You may also want to read these

There are none comment

Leave a comment

Your email address will not be published. Required fields are marked *