Back to Blog
Lesson 48 of the Intermediate Laravel: Real-World Application Patterns course
LaravelJune 26, 20263 min read

Building a Search API: Integrating Drivers & Indexing in Laravel

Stop relying on slow database LIKE queries. Learn how to integrate search drivers, index Eloquent models, and build a high-performance Search API in Laravel.

LaravelSearchAPIArchitectureScoutphpbackend

Previously in this course, we explored Database Indexing Strategies to speed up standard relational queries. While database indexes are essential for lookups, they struggle with full-text search requirements like fuzzy matching, relevance scoring, and multi-field weighted results.

In this lesson, we will move beyond WHERE title LIKE '%query%' and implement a dedicated search layer using Laravel Scout. This allows us to integrate powerful search engines like Meilisearch or Algolia, providing a fast, scalable search experience for our project board.

The Search Architecture

When building a search API, we decouple the storage of data from the retrieval of data. Our primary database remains the source of truth, but we maintain a secondary "search index" optimized for high-speed retrieval.

FeatureStandard Database QueryDedicated Search Engine
SpeedSlow on large datasetsNear-instant
Typo ToleranceNoneHigh
RelevanceBasic order byWeighted scoring
ComplexityHigh (complex SQL)Simple (API-based)

Integrating a Search Driver

We will use Laravel Scout, the official driver-based search abstraction. First, install the package and the Meilisearch engine driver:

Bash
composer require laravel/scout meilisearch/meilisearch-php
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

Next, configure your .env file to use the driver:

.env
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700

Indexing Your Eloquent Data

To make your Task model searchable, add the Laravel\Scout\Searchable trait. This trait hooks into model events—automatically syncing data to the search index whenever a record is created, updated, or deleted.

PHP
namespace App\Models;

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

class Task extends Model
{
    use Searchable;

    #6A9955">// Define which data goes into the index
    public function toSearchableArray(): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'description' => $this->description,
            'status' => $this->status,
        ];
    }
}

Now, run the import command to push your existing database records into the search engine:

Bash
php artisan scout:import "App\Models\Task"

Implementing the Search API

With the infrastructure in place, we can now create a clean endpoint. We’ll inject a SearchService (following the patterns established in our Service Layer lesson) to handle the search logic.

PHP
#6A9955">// app/Http/Controllers/Api/TaskSearchController.php
public function index(Request $request)
{
    $query = $request->input('q');
    
    #6A9955">// Perform the search
    $tasks = Task::search($query)
        ->where('status', 'active') #6A9955">// Filter by attribute
        ->paginate(15);

    return TaskResource::collection($tasks);
}

Hands-on Exercise

  1. Setup: If you haven't already, spin up a Meilisearch container using Docker.
  2. Refine: Modify your toSearchableArray method to include a project_name key by loading the project relationship.
  3. Test: Use Postman or Insomnia to hit your new search endpoint and verify that fuzzy matching works (e.g., searching "tasck" should return "task").

Common Pitfalls

  • Index Bloat: Don't index massive blobs of text or sensitive data. Only include fields required for search results.
  • Sync Latency: Remember that Searchable triggers on model events. In high-traffic apps, consider setting SCOUT_QUEUE=true to offload the indexing work to your background queues, as discussed in Asynchronous Processing with Queues.
  • Missing Imports: If you add the Searchable trait to an existing model, don't forget to run scout:import. New records will sync automatically, but old ones won't appear until you import them.

Recap

We’ve successfully decoupled our search logic from the primary database, integrated a professional search driver, and created a responsive API endpoint. By leveraging Scout, our project board can now handle complex, relevant searches without sacrificing performance.

Up next: We will address data integrity during high-load scenarios by Handling Concurrency and Race Conditions.

Similar Posts