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.

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

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

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
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Multi-tenant 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
Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate logo

Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate

Build your SaaS application in hours. Out-of-the-box multi-tenancy and seamless Stripe integration. Supports subscriptions and one-time purchases, allowing you to focus on building and creating without repetitive setup tasks.

Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate
Rector logo

Rector

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

Rector
MongoDB logo

MongoDB

Enhance your PHP applications with the powerful integration of MongoDB and Laravel, empowering developers to build applications with ease and efficiency. Support transactional, search, analytics and mobile use cases while using the familiar Eloquent APIs. Discover how MongoDB's flexible, modern database can transform your Laravel applications.

MongoDB

The latest

View all →
PHP 8.4 is released with Property Hooks, Class Instantiation without extra parenthesis, and more image

PHP 8.4 is released with Property Hooks, Class Instantiation without extra parenthesis, and more

Read article
Asymmetric Property Visibility in PHP 8.4 image

Asymmetric Property Visibility in PHP 8.4

Read article
Access Laravel Pulse Data as a JSON API image

Access Laravel Pulse Data as a JSON API

Read article
Laravel Forge adds Statamic Integration image

Laravel Forge adds Statamic Integration

Read article
Transform Data into Type-safe DTOs with this PHP Package image

Transform Data into Type-safe DTOs with this PHP Package

Read article
PHPxWorld - The resurgence of PHP meet-ups with Chris Morrell image

PHPxWorld - The resurgence of PHP meet-ups with Chris Morrell

Read article