The Laravel framework has become a go-to resource for developers building web services.

As an open-source tool, Laravel offers a myriad of out-of-the-box functionalities that enable developers to build robust and functional applications.

Among its offerings is Laravel Scout, a library for managing the search indexes for your application. Its flexibility lets developers fine-tune the configurations and select from Algolia, Meilisearch, MySQL, or Postgres drivers to store the indexes.

Here, we will explore this tool in-depth, teaching you how to add full-text search support to a Laravel application through the driver. You will model a demo Laravel application for storing the name of mockup trains and then use Laravel Scout to add a search to the application.

Prerequisites

To follow along, you should have:

  • The PHP compiler installed on your computer. This tutorial uses PHP version 8.1.
  • The Docker engine or Docker Desktop installed on your computer
  • An Algolia cloud account, which you can create for free

How To Install Scout in a Laravel Project

To use Scout, you must first create a Laravel application where you intend to add the search functionality. The Laravel-Scout Bash script contains the commands to generate a Laravel application within a Docker container. Using Docker means you don’t need to install additional supporting software, like a MySQL database.

The Laravel-scout script uses the Bash scripting language, so you must execute it within a Linux environment. If you’re running Windows, ensure you configure Windows Subsystem for Linux (WSL).

If using WSL, execute the following command in your terminal to set your preferred Linux distribution.

wsl -s ubuntu

Next, navigate to the location on your computer you would like to place the project. The Laravel-Scout script will generate a project directory here. In the example below, the Laravel-Scout script would create a directory within the desktop directory.

cd /desktop

Run the command below to execute the Laravel-Scout script. It will scaffold a Dockerized application with the necessary boilerplate code.

curl -s https://laravel.build/laravel-scout-app | bash

After the execution, change your directory using cd laravel-scout-app. Then, run the sail-up command within the project folder to start the Docker containers for your application.

Note: On many Linux distributions, you may need to run the command below with the sudo command to initiate elevated privileges.

./vendor/bin/sail up

You might encounter an error:

Error stating the port is allocated
Error stating the port is allocated.

To resolve this, use the APP_PORT variable to specify a port within the sail up command:

APP_PORT=3001 ./vendor/bin/sail up

Next, execute the command below to run the application through Artisan on the PHP server.

php artisan serve
Serving the Laravel application with Artisan
Serving the Laravel application with Artisan

From your web browser, navigate to the running application at http://127.0.0.1:8000. The application will display the Laravel welcome page at the default route.

Welcome page of the Laravel application
Welcome page of the Laravel application

How To Add Laravel Scout to the Application

In your terminal, enter the command to enable the Composer PHP package manager to add Laravel Scout to the project.

composer require laravel/scout

Next, publish the Scout configuration file using the vendor:publish command. The command will publish the scout.php configuration file to your application’s config directory.

 php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

Now, modify the boilerplate .env file to contain a SCOUT_QUEUE boolean value.

The SCOUT_QUEUE value will allow Scout to queue operations, providing better response times. Without it, Scout drivers like Meilisearch won’t reflect new records immediately.

SCOUT_QUEUE=true

Also, modify the DB_HOST variable in the .env file to point to your localhost to use the MySQL database within the Docker containers.

DB_HOST=127.0.0.1

How To Mark a Model and Configure the Index

Scout doesn’t enable searchable data models by default. You must explicitly mark a model as searchable using its Laravel\Scout\Searchable trait.

You’ll start by creating a data model for a demo Train application and marking it as searchable.

How To Create a Model

For the Train application, you’ll want to store the placeholder names of each available train.

Execute the Artisan command below to generate the migration and name it create_trains_table.

php artisan make:migration create_trains_table 
Making a migration named create_trains_table
Making a migration named create_trains_table

The migration will be generated in a file whose name combines the name specified and the current timestamp.

Open the migration file located in the database/migrations/ directory.

To add a title column, add the following code after the id() column in line 17. The code will add a title column.

$table->string('title');

To apply the migration, execute the command below.

php artisan migrate
Applying the Artisan migration
Applying the Artisan migration

After running the database migrations, create a file named Train.php in the app/Models/ directory.

How To Add the Laravel\Scout\Searchable Trait

Mark the Train model for search by adding the Laravel\Scout\Searchable trait to the model, as shown below.

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Train extends Model
{
    use Searchable;
    public $fillable = ['title'];

Also, you need to configure the search indexes by overriding the searchableAs method. The default behavior of Scout would persist the model to match the model table name.

So, add the following code to the Train.php file below the code from the previous block.

/**
  * Retrieve the index name for the model.
  *
  * @return string
 */
 public function searchableAs()
    {
        return 'trains_index';
   }
}

How To Use Algolia with Scout

For the first full-text search with Laravel Scout, you’ll use the Algolia driver. Algolia is a software as a service (SaaS) platform used to search through large amounts of data. It provides a web dashboard for developers to manage their search indexes and a robust API that you can access via a software development kit (SDK) in your preferred programming language.

Within the Laravel application, you will use the Algolia client package for PHP.

How To Set Up Algolia

First, you must install the Algolia PHP search client package for your application.

Execute the command below.

composer require algolia/algoliasearch-client-php

Next, you must set your Application ID and Secret API Key credentials from Algolia in the .env file.

Using your web browser, navigate to your Algolia dashboard to obtain the Application ID and Secret API Key credentials.

Click on Settings at the bottom of the left-hand sidebar to navigate to the Settings page.

Next, click API Keys within the Team and Access section of the Settings page to view the keys for your Algolia account.

Navigating to the API Keys page on Algolia Cloud
API Keys page on Algolia Cloud

At the API Keys page, note the Application ID and Admin API Key values. You will use these credentials to authenticate the connection between the Laravel application and Algolia.

Viewing the Application ID and Admin API Keys from the Algolia API Keys page
Application ID and Admin API Keys

Add the code below to your .env file using your code editor and replace the placeholders with the corresponding Algolia API secrets.

ALGOLIA_APP_ID=APPLICATION_ID
ALGOLIA_SECRET=ADMIN_API_KEY

Also, replace the SCOUT_DRIVER variable with the code below to change the value from meilisearch to algolia. Changing this value will instruct Scout to use the Algolia driver.

SCOUT_DRIVER=algolia

How To Create the Application Controllers

Within the app/Http/Controllers/ directory, create a TrainSearchController.php file to store a controller for the application. The controller will list and add data to the Train model.

Add the following code block into the TrainSearchController.php file to build the controller.

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Models\Train;

class TrainSearchController extends Controller
{
    /**
     * Compile the content for a trains list view.
     *
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
    */
    public function index(Request $request)
    {
        if($request->has('titlesearch')){
            $trains = Train::search($request->titlesearch)
                ->paginate(6);
        }else{
            $trains = Train::paginate(6);
        }
        return view('Train-search',compact('trains'));
    }

    /**
     * Create a new train entry.
     *
     * @return \Illuminate\Http\RedirectResponse
    */
    public function create(Request $request)
    {
        $this->validate($request,['title'=>'required']);

        $trains = Train::create($request->all());
        return back();
    }
}

How To Create the Application Routes

In this step, you’ll create the routes for listing and adding new trains to the database.

Open your routes/web.php file and replace the existing code with the block below.

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TrainSearchController;

Route::get('/', function () {
    return view('welcome');
});

Route::get('trains-lists', [TrainSearchController::class, 'index']) -> name ('trains-lists');

Route::post('create-item', [TrainSearchController::class, 'create']) -> name ('create-item');

The code above defines two routes in the application. The GET request for the /trains-lists route lists all stored train data. The POST request for the /create-item route creates new train data.

How To Create the Application Views

Create a file within the resources/views/ directory and name it Train-search.blade.php. The file will display the user interface for the search functionality.

Add the content of the code block below into the Train-search.blade.php file to create a single page for the search functionality.

<!DOCTYPE html>
<html>
<head>
    <title>Laravel - Laravel Scout Algolia Search Example</title>
    <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body>
<div class="container">
    <h2 class="text-bold">Laravel Full-Text Search Using Scout </h2><br/>
    <form method="POST" action="{{ route('create-item') }}" autocomplete="off">
        @if(count($errors))
            <div class="alert alert-danger">
                <strong>Whoops!</strong> There is an error with your input.
                <br/>
                <ul>
                    @foreach($errors->all() as $error)
                    <li>{{ $error }}</li>
                    @endforeach
                </ul>
            </div>
        @endif

        <input type="hidden" name="_token" value="{{ csrf_token() }}">

        <div class="row">
            <div class="col-md-6">
                <div class="form-group {{ $errors->has('title') ? 'has-error' : '' }}">
                    <input type="text" id="title" name="title" class="form-control" placeholder="Enter Title" value="{{ old('title') }}">
                    <span class="text-danger">{{ $errors->first('title') }}</span>
                </div>
            </div>
            <div class="col-md-6">
                <div class="form-group">
                    <button class="btn btn-primary">Create New Train</button>
                </div>
            </div>
        </div>
    </form>

    <div class="panel panel-primary">
      <div class="panel-heading">Train Management</div>
      <div class="panel-body">
            <form method="GET" action="{{ route('trains-lists') }}">

                <div class="row">
                    <div class="col-md-6">
                        <div class="form-group">
                            <input type="text" name="titlesearch" class="form-control" placeholder="Enter Title For Search" value="{{ old('titlesearch') }}">
                        </div>
                    </div>
                    <div class="col-md-6">
                        <div class="form-group">
                            <button class="btn btn-primary">Search</button>
                        </div>
                    </div>
                </div>
            </form>

            <table class="table">
                <thead>
                    <th>Id</th>
                    <th>Train Title</th>
                    <th>Creation Date</th>
                    <th>Updated Date</th>
                </thead>
                <tbody>
                    @if($trains->count())
                        @foreach($trains as $key => $item)
                            <tr>
                                <td>{{ ++$key }}</td>
                                <td>{{ $item->title }}</td>
                                <td>{{ $item->created_at }}</td>
                                <td>{{ $item->updated_at }}</td>
                            </tr>
                        @endforeach
                    @else
                        <tr>
                            <td colspan="4">No train data available</td>
                        </tr>
                    @endif
                </tbody>
            </table>
            {{ $trains->links() }}
      </div>
    </div>
</div>
</body>
</html>

The HTML code above contains a form element with an input field and a button for typing the title of a train before you save it to the database. The code also has an HTML table displaying the id, title, created_at, and updated_at details of a train entry within the database.

How To Use the Algolia Search

To view the page, navigate to http://127.0.0.1:8000/trains-lists from your web browser.

Viewing the Train model data displayed within the trains-lists page
Train model data

The database is currently empty, so you need to enter a title of a demo train in the input field and click Create New Train to save it.

Inserting a new train entry
Inserting a new train entry

To use the search feature, type a keyword from any saved train titles into the Enter Title For Search input field and click Search.

As shown in the image below, only search entries containing the keyword in their titles will display.

Using the search feature to find a train entry
Using the search feature to find a train entry

Meilisearch with Laravel Scout

Meilisearch is an open-source search engine focusing on speed, performance, and improved developer experience. It shares several features with Algolia, using the same algorithms, data structures, and research — but with a different programming language.

Developers can create and self-host a Meilisearch instance within their on-premises or cloud infrastructure. Meilisearch also has a beta cloud offering similar to Algolia for developers who want to use the product without managing its infrastructure.

In the tutorial, you already have a local instance of Meilisearch running within your Docker containers. You will now extend the Laravel Scout functionality to use the Meilisearch instance.

To add Meilisearch to the Laravel application, run the command below in your project terminal.

composer require meilisearch/meilisearch-php

Next, you need to modify the Meilisearch variables within the .env file to configure it.

Replace the SCOUT_DRIVER, MEILISEARCH_HOST, and MEILISEARCH_KEY variables in the .env file with the ones below.

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

The SCOUT_DRIVER key specifies the driver that Scout should use, while MEILISEARCH_HOST represents the domain where your Meilisearch instance is running. Although not required during development, adding the MEILISEARCH_KEY in production is recommended.

Note: Comment out the Algolia ID and Secret when using Meilisearch as your preferred driver.

After completing the .env configurations, you should index your pre-existing records using the Artisan command below.

php artisan scout:import "App\Models\Train"

Laravel Scout with Database Engine

Scout’s database engine might be most suitable for applications that use smaller databases or manage less intensive workloads. Currently, the database engine supports PostgreSQL and MySQL.

This engine uses “where-like” clauses and full-text indexes against your existing database, enabling it to find the most relevant search results. You don’t need to index your records when using the database engine.

To use the database engine, you must set your SCOUT_DRIVER .env variable to the database.

Open the .env file within the Laravel application and change the value of the SCOUT_DRIVER variable.

SCOUT_DRIVER = database

After changing your driver to the database, Scout will switch to using the database engine for full-text search.

Collection Engine with Laravel Scout

In addition to the database engine, Scout also offers a collection engine. This engine uses “where” clauses and collection filtering to extract the most relevant search results.

Unlike the database engine, the collection engine supports all relational databases that Laravel also supports.

You can use the collection engine by setting the SCOUT_DRIVER environment variable to collection or by manually specifying the collection driver in the Scout configuration file.

SCOUT_DRIVER = collection

Explorer with Elasticsearch

With the strength of Elasticsearch queries, Explorer is a modern Elasticsearch driver for Laravel Scout. It offers a compatible Scout driver and benefits like storing, searching, and analyzing massive amounts of data in real time. Elasticsearch with Laravel offers results in milliseconds.

To use the Elasticsearch Explorer driver in your Laravel application, you’ll need to configure the boilerplate docker-compose.yml file that the Laravel-Scout script generated. You’ll add the additional configurations for Elasticsearch and restart the containers.

Open your docker-compose.yml file and replace its contents with the following.

# For more information: https://laravel.com/docs/sail
version: '3'
services:
    laravel.test:
        build:
            context: ./vendor/laravel/sail/runtimes/8.1
            dockerfile: Dockerfile
            args:
                WWWGROUP: '${WWWGROUP}'
        image: sail-8.1/app
        extra_hosts:
            - 'host.docker.internal:host-gateway'
        ports:
            - '${APP_PORT:-80}:80'
            - '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
        environment:
            WWWUSER: '${WWWUSER}'
            LARAVEL_SAIL: 1
            XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
            XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
        volumes:
            - '.:/var/www/html'
        networks:
            - sail
        depends_on:
            - mysql
            - redis
            - meilisearch
            - mailhog
            - selenium
            - pgsql
            - elasticsearch

    mysql:
        image: 'mysql/mysql-server:8.0'
        ports:
            - '${FORWARD_DB_PORT:-3306}:3306'
        environment:
            MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
            MYSQL_ROOT_HOST: "%"
            MYSQL_DATABASE: '${DB_DATABASE}'
            MYSQL_USER: '${DB_USERNAME}'
            MYSQL_PASSWORD: '${DB_PASSWORD}'
            MYSQL_ALLOW_EMPTY_PASSWORD: 1
        volumes:
            - 'sail-mysql:/var/lib/mysql'
            - './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh'
        networks:
            - sail
        healthcheck:
            test: ["CMD", "mysqladmin", "ping", "-p${DB_PASSWORD}"]
            retries: 3
            timeout: 5s
            
    elasticsearch:
        image: 'elasticsearch:7.13.4'
        environment:
            - discovery.type=single-node
        ports:
            - '9200:9200'
            - '9300:9300'
        volumes:
            - 'sailelasticsearch:/usr/share/elasticsearch/data'
        networks:
            - sail
    kibana:
        image: 'kibana:7.13.4'
        environment:
            - elasticsearch.hosts=http://elasticsearch:9200
        ports:
            - '5601:5601'
        networks:
            - sail
        depends_on:
            - elasticsearch
    redis:
        image: 'redis:alpine'
        ports:
            - '${FORWARD_REDIS_PORT:-6379}:6379'
        volumes:
            - 'sail-redis:/data'
        networks:
            - sail
        healthcheck:
            test: ["CMD", "redis-cli", "ping"]
            retries: 3
            timeout: 5s
    pgsql:
        image: 'postgres:13'
        ports:
            - '${FORWARD_DB_PORT:-5432}:5432'
        environment:
            PGPASSWORD: '${DB_PASSWORD:-secret}'
            POSTGRES_DB: '${DB_DATABASE}'
            POSTGRES_USER: '${DB_USERNAME}'
            POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}'
        volumes:
            - 'sailpgsql:/var/lib/postgresql/data'
        networks:
            - sail
        healthcheck:
            test: ["CMD", "pg_isready", "-q", "-d", "${DB_DATABASE}", "-U", "${DB_USERNAME}"]
            retries: 3
            timeout: 5s
    meilisearch:
        image: 'getmeili/meilisearch:latest'
        ports:
            - '${FORWARD_MEILISEARCH_PORT:-7700}:7700'
        volumes:
            - 'sail-meilisearch:/meili_data'
        networks:
            - sail
        healthcheck:
            test: ["CMD", "wget", "--no-verbose", "--spider",  "http://localhost:7700/health"]
            retries: 3
            timeout: 5s
    mailhog:
        image: 'mailhog/mailhog:latest'
        ports:
            - '${FORWARD_MAILHOG_PORT:-1025}:1025'
            - '${FORWARD_MAILHOG_DASHBOARD_PORT:-8025}:8025'
        networks:
            - sail
    selenium:
        image: 'selenium/standalone-chrome'
        extra_hosts:
            - 'host.docker.internal:host-gateway'
        volumes:
            - '/dev/shm:/dev/shm'
        networks:
            - sail
networks:
    sail:
        driver: bridge
volumes:
    sail-mysql:
        driver: local
    sail-redis:
        driver: local
    sail-meilisearch:
        driver: local
    sailpgsql:
        driver: local
    sailelasticsearch:
        driver: local 

Next, run the command below to pull the new Elasticsearch image you added to the docker-compose.yml file.

docker-compose up

Then, execute the Composer command below to install Explorer into the project.

composer require jeroen-g/explorer

You also need to create a configuration file for the Explorer driver.

Execute the Artisan command below to generate an explorer.config file for storing the configurations.

php artisan vendor:publish --tag=explorer.config

The configuration file generated above will be available in the /config directory.

In your config/explorer.php file, you can reference your model using the indexes key.

'indexes' => [
        \App\Models\Train::class
],

Change the value of the SCOUT_DRIVER variable within the .env file to elastic to configure Scout to use the Explorer driver.

SCOUT_DRIVER = elastic

At this point, you’ll use Explorer within the Train model by implementing the Explorer interface and overriding the mappableAs() method.

Open the Train.php file within the App\Models directory and replace the existing code with the code below.

<?php
namespace App\Models;
 
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use JeroenG\Explorer\Application\Explored;
use Laravel\Scout\Searchable;
 
class Train extends Model implements Explored
{
    use HasFactory;
    use Searchable;
 
    protected $fillable = ['title'];
 
    public function mappableAs(): array
    {
        return [
        	'id'=>$this->Id,
        	'title' => $this->title,
        ];
    }
} 

With the code you have added above, you can now use Explorer to search text within the Train model.

Summary

For PHP developers, Laravel and add-ons like Scout make it a breeze to integrate fast, robust full-text search functionality. With the Database Engine, Collection Engine, and the capabilities of Meilisearch and Elasticsearch, you can interact with your app’s database and implement advanced search mechanisms in mere milliseconds.

Seamlessly managing and updating your database means your users receive an optimal experience while your code remains clean and efficient.

With our Application and Database Hosting solutions, Kinsta is your one-stop shop for all your modern Laravel development needs. The first $20 is on us.

Jeremy Holcombe Kinsta

Senior Editor at Kinsta, WordPress Web Developer, and Content Writer. Outside of all things WordPress, I enjoy the beach, golf, and movies. I also have tall people problems.