A practical guide to search Eloquent relationships using Laravel Scout Database Driver

Published on by

A practical guide to search Eloquent relationships using Laravel Scout Database Driver image

Laravel Scout is a tool for adding full-text search functionality to your Laravel applications. It uses drivers like Algolia, Meilisearch, SQL databases, and others to search through your Eloquent models. The package is highly customizable, making it a versatile option for implementing search capabilities in your application.

Let's examine how we can search for Eloquent Model relationships using the Laravel Scout with the Database driver. Remember, the Laravel Scout driver currently supports MySQL and PostgreSQL.

Let’s start by creating a new Laravel application. For this purpose, you can check the Laravel documentation.

Create Models and Migrations

We will make use of two Eloquent models:

  • Post
  • Category

Create Category Model

Run the following command to create the Category model:

php artisan make:model Category -m

This command will create a new Category model class in the app\Models directory and generate a migration file for creating the categories table in the database.

Open the migration file in the database/migrations directory and add the necessary columns for the categories table. For example:

public function up()
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}

Create Post Model

Run the following command to create the Post model:

php artisan make:model Post -m

The command will create a new Post model class in the app\Models directory and generate a migration file for creating the posts table in the database.

Open the migration file in the database/migrations directory and add the necessary columns for the posts table. For example:

public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
$table->foreignId('category_id')->constrained();
$table->timestamps();
});
}

Apply the Migrations

Assuming you have configured the database for the application, run the following command to apply the migrations and create the posts and categories tables in the database:

php artisan migrate

Generate dummy data using a Database Seeder

Let’s create a database seeder to generate some dummy records for posts and categories tables.

First, create a new seeder class by running the following artisan command:

php artisan make:seeder PostsCategoriesTableSeeder

Open the PostsCategoriesTableSeeder class located in the database/seeds directory and update the run method with the following code:

<?php
 
namespace Database\Seeders;
 
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
 
class PostsCategoriesTableSeeder extends Seeder
{
use WithoutModelEvents;
 
public function run()
{
// Generate 5 categories
$data = [
['name' => 'News'],
['name' => 'Sports'],
['name' => 'Entertainment'],
['name' => 'Technology'],
['name' => 'Business'],
];
 
DB::table('categories')->insert($data);
 
// Generate 1000 posts
for ($i = 1; $i <= 1000; $i++) {
DB::table('posts')->insert([
'title' => "Post $i",
'body' => "Content for post $i",
'category_id' => rand(1, 5)
]);
}
}
}

This seeder class generates five dummy categories and 1000 dummy posts. The name of each category is generated using a concatenation of a string and an automatically-incrementing id value, and the title, body, and category_id of each post are generated using a similar method. The category_id is chosen randomly from the list of categories.

To run the seeder, add the following line to the run method of the DatabaseSeeder class located in the database/seeds directory:

$this->call(PostsCategoriesTableSeeder::class);

Run the seeder by executing the following artisan command:

php artisan db:seed

That's all there is to it! Now, you have a new Laravel application with both Post and Category models with seeded dummy data.

Configure the Application to use Laravel Scout

To configure a Laravel application to use Laravel Scout with the Database Driver, you will need to follow these steps:

First, install the Laravel Scout package:

composer require laravel/scout

Next, publish the Scout configuration file:

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

Now in the .env file, set the SCOUT_DRIVER option to database

SCOUT_DRIVER=database

Next, add the Laravel\Scout\Searchable trait to your model(s) to make them searchable:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
 
class Post extends Model
{
use HasFactory;
use Searchable;
}

After that, define the category() relationship on the Post model as follows:

public function category()
{
return $this->belongsTo(Category::class);
}

Finally, define the toSearchableArray() method on your model to specify which fields should be indexed by Laravel Scout:

public function toSearchableArray()
{
return [
'title' => '',
'body' => '',
'categories.name' => '',
];
}

To use Laravel Scout to perform full-text search on your models, you need to define a toSearchableArray() method on your model. This method should return an array of data that Laravel Scout should index.

The toSearchableArray() method should include relevant data to the search query. For example, if you are searching for posts that belong to a particular category, you might include the name field of the Category model in the toSearchableArray() method.

That's it! You should now be able to use Laravel Scout to perform full-text searches on your models using the database driver.

Let's Search.

To start, create a new SearchController to test queries.

Create Controller

Run the following command:

php artisan make:controller SearchController --invokable

This will create a new SearchController class in the app/Http/Controllers directory. The controller class will contain a single __invoke() method called when the controller is invoked.

Add the Route

To use the SearchController in your application, you need to define a route for it in the routes/web.php file:

Route::get('/search', SearchController::class);

This route will map a GET request to the /search URL to the __invoke() method of the SearchController.

Write a Search Query

Open the SearchController class and add our first query using Laravel Scout.

<?php
 
namespace App\Http\Controllers;
 
use App\Models\Post;
use Illuminate\Http\Request;
 
class SearchController extends Controller
{
public function __invoke(Request $request)
{
$posts = Post::search(trim($request->get('search')) ?? '')
->query(function ($query) {
$query->join('categories', 'posts.category_id', 'categories.id')
->select(['posts.id', 'posts.title', 'posts.body', 'categories.name as category'])
->orderBy('posts.id', 'DESC');
})
->get();
 
return response()->json(data: $posts, status: 200);
}
}

The Post::search() method initiates a search query on the posts database table. This method returns an instance of a Builder class. This means that before actually executing the query by the DatabaseEngine you can apply several functions such as where(), whereIn(), onlyTrashed(), orderBy(), take(), paginate() and others to filter down your results further.

The trim($request->get('search')) ?? '' input parameter retrieves the query string search from the HTTP request and trims any leading or trailing whitespace or an empty string if the search query string is not present.

The query() function allows you to customize the search query by defining additional constraints or modifying the SELECT clause of the query. In this case, the query uses a JOIN clause to join with the categories table to search not only the posts table columns but also the name column of the categories table. Moreover, I am explicitly selecting what columns to return by prefixing the columns with the database name and adding an ORDER BY clause by specifying the full column name, including the database table. The query fails without prefixing the database table name.

Finally, the get() method is used to execute the search query and retrieve the matching posts from the database.

Let’s run the application in the browser and provide a search query string with the value of "technology" to return all posts with the category "technology."

The first part of the results.

The last part of the results.

Let’s run a query against the database to retrieve posts with the category "Technology" and compare.

Database results for the same query.

As you can see, both return the same result!

As you can see, the query() function on the search() method is a powerful tool for searching through relationships in your Laravel Scout database driver.

You can download a copy of the source code here at GitHub.

Bilal Haidar photo

Hey there 👋! I am Bilal, Founder & CEO at Let's Remote. I'm a highly skilled web developer with 16+ years of experience. Laravel is my bread and butter.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
Tinkerwell

Version 4 of Tinkerwell is available now. Get the most popular PHP scratchpad with all its new features and simplify your development workflow today.

Visit Tinkerwell
Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Lucky Media logo

Lucky Media

Bespoke software solutions built for your business. We ♥ Laravel

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
All Green logo

All Green

All Green is a SaaS test runner that can execute your whole Laravel test suite in mere seconds so that you don't get blocked – you get feedback almost instantly and you can deploy to production very quickly.

All Green
Larafast: Laravel SaaS Starter Kit logo

Larafast: Laravel SaaS Starter Kit

Larafast is a Laravel SaaS Starter Kit with ready-to-go features for Payments, Auth, Admin, Blog, SEO, and beautiful themes. Available with VILT and TALL stacks.

Larafast: Laravel SaaS Starter Kit
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector

The latest

View all →
Property Hooks Get Closer to Becoming a Reality in PHP 8.4 image

Property Hooks Get Closer to Becoming a Reality in PHP 8.4

Read article
Asserting Exceptions in Laravel Tests image

Asserting Exceptions in Laravel Tests

Read article
Reversible Form Prompts and a New Exceptions Facade in Laravel 11.4 image

Reversible Form Prompts and a New Exceptions Facade in Laravel 11.4

Read article
Basset is an alternative way to load CSS & JS assets image

Basset is an alternative way to load CSS & JS assets

Read article
Integrate Laravel with Stripe Connect Using This Package image

Integrate Laravel with Stripe Connect Using This Package

Read article
The Random package generates cryptographically secure random values image

The Random package generates cryptographically secure random values

Read article