Laravel Projects Examples

Laravel Search by Query/Tag With Auto Complete - Laravel Scout and Alpine.js

This project demonstrates how to add search with suggestions using Laravel Scout and Alpine.js.

How It Works

For this example, a database scout driver is used.

// ...
 
SCOUT_DRIVER=database

The Searchable trait must be added to the Eloquent models from which search you will want to do. A good practice would be to add searchable fields to the toSearchableArray() method as an array in the model.

app/Models/Category.php:

use Laravel\Scout\Searchable;
use Illuminate\Database\Eloquent\Model;
 
class Category extends Model
{
use Searchable;
 
// ...
 
public function toSearchableArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
];
}
}

app/Models/Product.php:

use Laravel\Scout\Searchable;
use Illuminate\Database\Eloquent\Model;
 
class Product extends Model
{
use Searchable;
 
// ...
 
public function toSearchableArray(): array
{
return [
'id' => $this->id,
'title' => $this->title,
'code' => $this->code,
];
}
}

A Blade component is used to add a search input. In the Blade component, Alpine.js makes a call to the /search endpoint. The response from a search call is saved to a results variable.

If categories or products exist in the result, they are looped through separately to show a result.

<div class="flex items-center min-w-96">
<div x-data="{
query: '',
results: { categories: [], products: [] },
isLoading: false,
resetSearch() {
this.query = '';
this.results = { categories: [], products: [] };
},
async search() {
if (this.query.length < 2) {
this.results = { categories: [], products: [] };
return;
}
 
this.isLoading = true;
 
try {
const url = new URL(@js(route('search')));
url.searchParams.set('query', this.query);
const response = await fetch(url);
this.results = await response.json();
} catch (error) {
console.error('Search failed:', error);
}
 
this.isLoading = false;
}
}"
@click.away="resetSearch()"
@keydown.escape.window="resetSearch()"
class="relative w-full"
>
<x-text-input
type="text"
x-model="query"
@input.debounce.300ms="search()"
placeholder="Search..."
class="w-full"
/>
 
<div x-show="query.length >= 2"
x-transition
class="absolute z-50 mt-1 w-full rounded border bg-white text-sm shadow-lg">
 
<template x-if="results.categories?.length">
<div class="p-2">
<template x-for="category in results.categories" :key="category.id">
<a :href="`/categories/${category.slug}`"
class="block border-b p-2 last:border-b-0 hover:bg-gray-100"
x-text="category.name">
</a>
</template>
</div>
</template>
 
<template x-if="results.products?.length">
<div class="p-2">
<h3 class="mb-2 font-bold">Products found</h3>
<template x-for="product in results.products" :key="product.id">
<a :href="`/products/${product.slug}`" class="flex items-center justify-between p-2 hover:bg-gray-100">
<span x-text="product.title"></span>
<span class="text-gray-600" x-text="`$${product.price}`"></span>
</a>
</template>
</div>
</template>
 
<div x-show="isLoading" class="p-4 text-center">
Loading...
</div>
</div>
</div>
</div>

The /search endpoint goes to the SearchController.

routes/web.php:

use App\Http\Controllers\SearchController;
 
Route::get('search', SearchController::class)->name('search');

In the SearchController, a search is done for a given query. The search() method on a model comes from a Laravel Scout. The returned result is a JSON.

app/Http/Controllers/SearchController.php:

use App\Models\Product;
use App\Models\Category;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
 
class SearchController extends Controller
{
public function __invoke(Request $request): JsonResponse
{
$categories = Category::search($request->input('query'))
->take(5)
->get();
 
$products = Product::search($request->input('query'))
->take(10)
->get();
 
return response()->json([
'categories' => $categories,
'products' => $products,
]);
}
}