Laravel Scout adalah solusi resmi Laravel untuk menambahkan fitur pencarian pada model Eloquent dengan API yang konsisten. Dengan Scout, Anda tidak perlu menulis integrasi pencarian dari nol untuk tiap search engine. Anda cukup menandai model sebagai searchable, lalu Scout akan menangani proses sinkronisasi data ke index pencarian.

Artikel ini membahas Laravel Scout dari dasar hingga implementasi praktis. Fokusnya adalah bagaimana Scout bekerja, kapan sebaiknya dipakai, perbedaan antar driver, serta langkah setup lengkap menggunakan contoh model Product.

Apa Itu Full-Text Search dan Kapan Memakai Laravel Scout?

Full-text search adalah teknik pencarian teks yang lebih baik daripada query SQL biasa seperti LIKE '%kata%'. Search engine modern dapat menangani tokenisasi, ranking relevansi, typo tolerance, prefix search, filtering, dan performa pencarian yang lebih baik untuk data besar.

Tanpa Scout, banyak aplikasi Laravel memulai pencarian seperti ini:

Product::where('name', 'like', "%{$keyword}%")->get();

Pendekatan ini sederhana, tetapi cepat memiliki keterbatasan:

  • Kurang akurat untuk pencarian multi-kata.
  • Sulit menangani ranking relevansi.
  • Kurang efisien pada dataset besar.
  • Tidak punya fitur pencarian lanjutan seperti typo tolerance atau faceting.

Kapan memakai Laravel Scout?

  • Ketika Anda ingin API pencarian yang konsisten di model Eloquent.
  • Ketika aplikasi mulai membutuhkan pencarian yang lebih baik daripada LIKE.
  • Ketika Anda ingin integrasi dengan search engine seperti Meilisearch atau Algolia.
  • Ketika data perlu otomatis di-index saat model dibuat, diubah, atau dihapus.

Kapan Scout mungkin belum perlu?

  • Jika datanya kecil dan pencarian hanya satu-dua kolom.
  • Jika Anda hanya butuh filter database biasa, bukan pencarian teks relevan.
  • Jika infrastruktur ingin tetap sesederhana mungkin dan belum siap menambah service search engine.

Memahami Driver Laravel Scout

Laravel Scout mendukung beberapa driver. Untuk panduan pemula, yang paling penting dipahami adalah perbedaan antara database driver dan search engine seperti Meilisearch atau Algolia.

1. Database Driver

Driver ini menggunakan database aplikasi untuk menjalankan pencarian. Keuntungannya:

  • Paling mudah dipasang.
  • Tidak perlu service tambahan.
  • Cocok untuk development, demo, atau kebutuhan sederhana.

Kekurangannya:

  • Fitur search terbatas dibanding search engine khusus.
  • Skalabilitas dan relevansi biasanya kalah dibanding Meilisearch/Algolia.
  • Tidak selalu cocok untuk use case full-text search yang lebih kompleks.

2. Meilisearch

Meilisearch adalah search engine open-source yang populer untuk Laravel karena setup relatif mudah dan performanya baik. Cocok jika Anda butuh:

  • Pencarian cepat.
  • Relevansi yang lebih baik.
  • Filter dan sorting yang fleksibel.
  • Self-hosted tanpa bergantung ke layanan pihak ketiga berbayar.

3. Algolia

Algolia adalah layanan search hosted yang matang dan kaya fitur. Cocok untuk:

  • Produk production yang butuh reliabilitas tinggi.
  • Tim yang ingin minim mengelola infrastruktur search.
  • Kebutuhan ranking, typo tolerance, analytics, dan fitur pencarian lanjutan.

Kekurangannya adalah biaya dan ketergantungan pada layanan eksternal.

Pilih yang Mana?

  • Database driver: belajar, prototipe, aplikasi kecil, atau kebutuhan sederhana.
  • Meilisearch: kebanyakan aplikasi production yang butuh search serius tapi tetap ingin kontrol infrastruktur.
  • Algolia: jika Anda ingin layanan managed dengan fitur enterprise-grade.

Instalasi Laravel Scout

Langkah pertama, pasang package Scout:

composer require laravel/scout

Lalu publish file konfigurasinya:

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

Setelah itu, Anda akan mendapatkan file config/scout.php.

Contoh konfigurasi dasar config/scout.php

<?php

return [
    'driver' => env('SCOUT_DRIVER', 'database'),

    'prefix' => env('SCOUT_PREFIX', ''),

    'queue' => env('SCOUT_QUEUE', false),

    'after_commit' => false,

    'chunk' => [
        'searchable' => 500,
        'unsearchable' => 500,
    ],

    'soft_delete' => false,
];

Nilai konfigurasi terpenting untuk pemula adalah:

  • driver: menentukan backend pencarian.
  • queue: apakah sinkronisasi index dijalankan lewat queue.
  • prefix: berguna jika satu search engine dipakai beberapa environment.

Konfigurasi environment untuk database driver

SCOUT_DRIVER=database
SCOUT_QUEUE=false
SCOUT_PREFIX=myapp_

Jika memakai Meilisearch, biasanya Anda akan menambahkan variabel seperti:

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

Jika memakai Algolia, variabelnya berbeda sesuai kredensial Algolia.

Catatan: untuk production, sebaiknya aktifkan queue agar proses indexing tidak memperlambat request web, terutama saat data diubah dalam jumlah besar.

Membuat Contoh Model Product yang Searchable

Sekarang kita buat contoh nyata. Misalnya aplikasi e-commerce sederhana dengan model Product.

Buat model, migration, dan seeder

php artisan make:model Product -m
php artisan make:seeder ProductSeeder

Contoh migration database/migrations/xxxx_xx_xx_create_products_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug')->unique();
            $table->text('description')->nullable();
            $table->decimal('price', 12, 2);
            $table->string('category')->nullable();
            $table->boolean('is_active')->default(true);
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};

Contoh seeder database/seeders/ProductSeeder.php

<?php

namespace Database\Seeders;

use App\Models\Product;
use Illuminate\Database\Seeder;
use Illuminate\Support\Str;

class ProductSeeder extends Seeder
{
    public function run(): void
    {
        $products = [
            [
                'name' => 'Laptop Pro 14',
                'slug' => 'laptop-pro-14',
                'description' => 'Laptop ringan untuk developer dan pekerja kreatif.',
                'price' => 18999000,
                'category' => 'Laptop',
                'is_active' => true,
            ],
            [
                'name' => 'Mechanical Keyboard RGB',
                'slug' => 'mechanical-keyboard-rgb',
                'description' => 'Keyboard mekanikal dengan switch tactile dan lampu RGB.',
                'price' => 1250000,
                'category' => 'Aksesoris',
                'is_active' => true,
            ],
            [
                'name' => 'Wireless Mouse Silent',
                'slug' => 'wireless-mouse-silent',
                'description' => 'Mouse nirkabel dengan klik senyap untuk kerja kantor.',
                'price' => 350000,
                'category' => 'Aksesoris',
                'is_active' => true,
            ],
        ];

        foreach ($products as $product) {
            Product::updateOrCreate(['slug' => $product['slug']], $product);
        }
    }
}

Jalankan migration dan seeder:

php artisan migrate
php artisan db:seed --class=ProductSeeder

Setup model app/Models/Product.php

<?php

namespace App\Models;

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

class Product extends Model
{
    use Searchable;

    protected $fillable = [
        'name',
        'slug',
        'description',
        'price',
        'category',
        'is_active',
    ];

    public function toSearchableArray(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'slug' => $this->slug,
            'description' => $this->description,
            'category' => $this->category,
            'price' => (float) $this->price,
            'is_active' => (bool) $this->is_active,
        ];
    }

    public function shouldBeSearchable(): bool
    {
        return $this->is_active;
    }
}

Ada dua method penting di sini:

  • toSearchableArray(): menentukan field apa saja yang dikirim ke index.
  • shouldBeSearchable(): menentukan apakah record layak masuk index.

Ini penting karena index pencarian sebaiknya hanya berisi data yang memang boleh dicari, misalnya produk aktif saja.

Import Data ke Index dan Sinkronisasi Otomatis

Import seluruh data lama

Jika data sudah ada sebelum Scout diaktifkan, Anda perlu melakukan import awal:

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

Jika ingin menghapus seluruh index model tertentu lalu membangun ulang, alurnya biasanya:

php artisan scout:flush "App\Models\Product"
php artisan scout:import "App\Models\Product"

Mengapa perlu import? Karena Scout hanya otomatis menyinkronkan perubahan setelah model menggunakan trait Searchable. Data lama tidak masuk index sampai Anda mengimpornya.

Update index otomatis saat create/update/delete

Setelah model memakai Searchable, Scout akan memantau event Eloquent. Artinya:

  • Create: record baru akan di-index.
  • Update: data index diperbarui.
  • Delete: document dihapus dari index.

Contoh:

$product = Product::create([
    'name' => 'Monitor 27 Inch IPS',
    'slug' => 'monitor-27-inch-ips',
    'description' => 'Monitor IPS untuk desain dan programming.',
    'price' => 2999000,
    'category' => 'Monitor',
    'is_active' => true,
]);

$product->update([
    'price' => 2799000,
]);

$product->delete();

Jika queue diaktifkan, sinkronisasi ini biasanya tidak langsung dieksekusi di request yang sama, melainkan dikirim ke worker queue.

Mengaktifkan queue untuk indexing

Di .env:

SCOUT_QUEUE=true
QUEUE_CONNECTION=database

Lalu siapkan table queue jika belum ada:

php artisan queue:table
php artisan migrate

Jalankan worker:

php artisan queue:work

Kesalahan yang sangat umum adalah mengaktifkan SCOUT_QUEUE=true tetapi lupa menjalankan worker. Akibatnya, data terasa “tidak terindex”, padahal job hanya menumpuk di queue.

Membuat Fitur Search di Controller, Route, dan View/API

Controller

php artisan make:controller ProductSearchController
<?php

namespace App\Http\Controllers;

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

class ProductSearchController extends Controller
{
    public function index(Request $request)
    {
        $q = trim((string) $request->get('q', ''));

        $products = $q !== ''
            ? Product::search($q)->get()
            : Product::query()->where('is_active', true)->latest()->limit(20)->get();

        return view('products.search', [
            'products' => $products,
            'q' => $q,
        ]);
    }

    public function api(Request $request)
    {
        $q = trim((string) $request->get('q', ''));

        $products = $q !== ''
            ? Product::search($q)->get()
            : collect();

        return response()->json([
            'query' => $q,
            'count' => $products->count(),
            'data' => $products,
        ]);
    }
}

Route

<?php

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

Route::get('/products/search', [ProductSearchController::class, 'index']);
Route::get('/api/products/search', [ProductSearchController::class, 'api']);

Blade view resources/views/products/search.blade.php

<form method="GET" action="{{ url('/products/search') }}">
    <input type="text" name="q" value="{{ $q }}" placeholder="Cari produk...">
    <button type="submit">Cari</button>
</form>

@if($q !== '')
    <p>Hasil pencarian untuk: <strong>{{ $q }}</strong></p>
@endif

@forelse($products as $product)
    <div style="margin-bottom: 16px;">
        <h3>{{ $product->name }}</h3>
        <p>{{ $product->description }}</p>
        <p>Kategori: {{ $product->category }} | Harga: Rp{{ number_format($product->price', 0, ',', '.') }}</p>
    </div>
@empty
    <p>Tidak ada produk ditemukan.</p>
@endforelse

Contoh response API:

{
  "query": "keyboard",
  "count": 1,
  "data": [
    {
      "id": 2,
      "name": "Mechanical Keyboard RGB",
      "slug": "mechanical-keyboard-rgb",
      "description": "Keyboard mekanikal dengan switch tactile dan lampu RGB.",
      "price": "1250000.00",
      "category": "Aksesoris",
      "is_active": true,
      "created_at": "2026-01-01T00:00:00.000000Z",
      "updated_at": "2026-01-01T00:00:00.000000Z"
    }
  ]
}

Catatan penting: pada implementasi nyata, format data API biasanya dibungkus dengan API Resource agar lebih konsisten dan aman.

Praktik Baik, Trade-off, dan Tips Debugging

1. Jangan masukkan semua field ke index

Hanya index field yang memang dibutuhkan untuk pencarian atau filtering. Semakin banyak field, semakin besar ukuran index dan biaya sinkronisasi.

2. Gunakan shouldBeSearchable() untuk menyaring data

Ini membantu mencegah draft, produk nonaktif, atau data sensitif ikut terindex.

3. Pahami perbedaan hasil antara database driver dan search engine

Query Product::search('laptop') bisa menghasilkan perilaku berbeda tergantung driver. Database driver lebih sederhana, sedangkan Meilisearch atau Algolia biasanya punya relevansi dan fitur matching yang lebih kaya. Jadi, jangan mengasumsikan semua driver memberikan hasil identik.

4. Gunakan queue di production

Tanpa queue, request create/update besar bisa melambat karena aplikasi menunggu proses index selesai.

5. Reimport setelah mengubah struktur data index

Jika Anda mengubah isi toSearchableArray(), field lama di index mungkin tidak lagi sesuai. Solusi paling aman biasanya flush lalu import ulang.

Checklist Kesalahan Umum

  • Lupa menjalankan queue worker saat SCOUT_QUEUE=true.
  • Lupa import data lama dengan php artisan scout:import.
  • Model belum memakai trait Searchable.
  • Method shouldBeSearchable() mengembalikan false sehingga data tidak masuk index.
  • Mismatch field antara data di model dan field yang diharapkan di search engine.
  • Konfigurasi .env salah, misalnya SCOUT_DRIVER tidak sesuai.
  • Lupa restart worker setelah mengubah konfigurasi.
  • Mengandalkan database driver untuk kebutuhan search kompleks padahal butuh Meilisearch atau Algolia.
  • Data soft delete tidak ditangani dengan benar jika aplikasi memakai soft deletes.

Penutup

Laravel Scout memberi cara yang rapi untuk menambahkan pencarian ke model Eloquent tanpa mengikat aplikasi ke implementasi search tertentu di level kode aplikasi. Untuk belajar atau kebutuhan sederhana, database driver cukup membantu. Namun ketika kebutuhan pencarian makin kompleks, Meilisearch atau Algolia biasanya menjadi pilihan yang lebih tepat.

Mulailah dari model yang benar-benar membutuhkan search, definisikan field index dengan hati-hati lewat toSearchableArray(), lalu pastikan proses import dan queue berjalan. Dengan pola ini, Anda bisa membangun fitur pencarian yang lebih cepat dirawat dan lebih siap untuk dikembangkan ke production.