- Laravel allows you to implement everything from simple search engines with AJAX to advanced full-text searches using Laravel Scout and external search engines like Algolia, Meilisearch, or Elasticsearch.
- For lightweight searches, filtering on the frontend with Alpine.js or with native fetch requests avoids overloading the server and improves the user experience in small lists.
- Laravel Scout centralizes integration with different search engines and makes it easy to mark models as searchable, manage indexes, and launch queries uniformly.
- The choice of engine (SaaS, open source or database) should be based on the volume of data, the complexity of the searches and the performance and maintenance requirements of the project.
When you start working with Laravel and need a real-time search engine that responds instantlyIt's easy to get lost among a thousand possible approaches: AJAX with fetch, jQuery, Alpine.js, Scout with Algolia or Meilisearch, frontend filtering, etc. The good news is that the Laravel ecosystem already provides practically everything you need to set up a smooth and fast search without dying in the attempt.
In this article you will see how to assemble different types of real-time search in LaravelFrom classic AJAX autocomplete to full-text searches with Laravel Scout and search engines like Algolia, Meilisearch, the database itself, or even Elasticsearch. You'll also see lightweight alternatives with Alpine.js for filtering data directly in the browser when the data volume is small.
What is a real-time search in Laravel and how does the basics work?
The idea behind a real-time search is that, as the user types in a text fieldA query is triggered and the results are updated without reloading the page. Technically, this involves three key components: the Laravel backend, the browser's JavaScript, and the exchange of data in JSON format.
On one hand, Laravel acts as the server layer It is responsible for receiving requests, interpreting search parameters (the text entered), querying the database, and returning a structured response, usually in JSON format. This response can indicate success, error, or that no results were found.
At the other end, JavaScript is responsible for listening to user events. On the search input, send asynchronous requests (AJAX) to the backend and display the returned data on the page without the browser performing a full refresh. This can be done with native fetch, jQuery AJAX, or small reactive libraries like Alpine.js.
With this basic mechanism you can build from a Simple autocomplete with a few records, up to an advanced full-text search engine with relevance, pagination and filters, supported by libraries like Laravel Scout and external search engines optimized for search.
Model, routes, and controller for a basic real-time search engine
Before you delve into JavaScript, you need to make sure the Laravel side is well organized: an Eloquent model to search on, clear routes, and a dedicated controller to manage the search logic in real time.
The first step is to have an Eloquent model that represents the table where you're going to search. Imagine a table of countries and a model called Country Very simple, without timestamps and with bulk assignment allowed:
Example of a minimal Eloquent model for searches:
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Pais extends Model
{
use HasFactory;
protected $guarded = [];
public $timestamps = false;
}
It is indicated here that the model Pais is located in the standard Laravel namespaceIt inherits from Model and allows you to assign any field with create() by leaving the guarded array empty. By disabling timestamps with public $timestamps = false, you avoid problems if the table does not have the created_at and updated_at columns.
The next step is to define the routes that will handle both the search engine display and the AJAX requestsA very common scheme combines a GET route to display the view and a POST route designed to receive real-time queries:
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\BuscadorController;
Route::get('/', function () {
return view('welcome');
});
Route::get('buscador', [BuscadorController::class, 'index']);
Route::post('buscador', [BuscadorController::class, 'buscar']);
The root route returns a welcome view, while the URL /search is reserved for search functionalityThe controller's index() method displays the form and search input, while the search() method processes asynchronous requests sent from the browser.
In the controller you can implement a very practical pattern: Prepare a default response array in case of error and only overwrite it when it is indeed a valid AJAX request and the query executes without problems.
The controller might have a similar structure this:
namespace App\Http\Controllers;
use App\Models\Pais;
use Illuminate\Http\Request;
class BuscadorController extends Controller
{
public function index()
{
return view('welcome');
}
public function buscar(Request $request)
{
$response = [
'success' => false,
'message' => 'Hubo un error',
];
if ($request->ajax()) {
$data = Pais::where('nombre', 'like', $request->texto.'%')
->take(10)
->get();
$response = [
'success' => true,
'message' => 'Consulta correcta',
'data' => $data,
];
}
return response()->json($response);
}
}
At this point you already have the complete backend cycle: incoming AJAX request, checking that it is AJAX, querying with where like and limiting results to a reasonable number using take(10) to avoid overloading the database. The response is always sent in JSON, which greatly simplifies the frontend's work.
Blade view and JavaScript fetch for reactive search
With the model, routes, and controller ready, it's time to build the visible part: a form with a search field and a block to display the results, plus the JavaScript responsible for making requests in the background.
The Blade view can be very simple, relying on the CSRF token which Laravel injects to validate POST requests and in a search input that is convenient to use:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<strong><meta name="csrf-token" content="{{ csrf_token() }}"></strong>
<title>Laravel</title>
</head>
<body>
<form action="" method="post">
<input type="search" name="texto" id="buscar">
</form>
<div id="resultado"></div>
<script>
window.addEventListener('load', function () {
const buscar = document.getElementById('buscar');
const resultado = document.getElementById('resultado');
buscar.addEventListener('keyup', function () {
fetch('/buscador', {
method: 'post',
body: JSON.stringify({ texto: buscar.value }),
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': document.head.querySelector('[name~="csrf-token"][content]').content,
},
})
.then(response => response.json())
.then(data => {
let html = '';
if (data.success) {
html += '<ul>';
for (let i in data.data) {
html += '<li>' + data.data[i].nombre + '</li>';
}
html += '<ul>';
} else {
html += 'No existen resultados';
}
resultado.innerHTML = html;
});
});
});
</script>
</body>
</html>
In this example the script Listen for the keyup event on the search inputEach keystroke triggers a fetch request to the /search path. The current text of the field is sent in JSON format, and key headers such as X-Requested-With are included to indicate that it is AJAX, along with the CSRF token to bypass Laravel's native protection.
When the response arrives, it is transformed into JSON and generated dynamically. a small HTML list with the resultsor a message like “No results found” when the query returns no data. All of this without reloading the page, in a natural way for the user.
This pattern can be further refined with small UX details, such as adding a delay (debounce) between keystrokes, display a loader or handle network errors to prevent the interface from appearing frozen when something fails.
Live search with Laravel and AJAX using jQuery
Although fetch has gained a lot of ground nowadays, jQuery AJAX remains very popular in legacy projects or on teams that already have it implemented. The idea is exactly the same: capture what the user types, make an asynchronous request, and refresh the DOM.
A typical workflow with jQuery in Laravel for live search usually includes these basic steps: define a specific route, create a dedicated controller, build the Blade view with the search input And finally, add the jQuery code that triggers AJAX as it is typed.
The process works like this: when the user starts typing, jQuery sends a query to the server with the search string. Laravel filters the information in the database, returns a JSON with the matching results, and jQuery updates an HTML container on the page to reflect the matches, all in a matter of milliseconds.
The advantage of using jQuery is that It pretty much summarizes the syntax of AJAX And it's very straightforward to read if you already have the library in your project. However, you're adding an extra dependency that might not be necessary if you can work with modern JavaScript and native fetch.
Real-time filtering and search on the frontend with Alpine.js
When the data to be displayed is relatively small (for example, less than 50 items), it's not always worth setting up a backend with complex searches. In those cases, a very convenient option is Filter directly in the browser with Alpine.js, without making requests to the server while the user types.
The idea is to pre-calculate a search string for each element (for example, name, description, and category in lowercase), store it in a data-search-text attribute, and let Alpine.js handle the rest. show or hide elements according to the written text in a search field.
The Alpine.js component can have a structure similar to this: filterItems
{
search: '',
hasResults: true,
selectedValue: '',
init() {
this.$watch('search', () => this.filterItems());
this.$nextTick(() => this.$refs.searchInput?.focus());
},
filterItems() {
const searchLower = this.search.toLowerCase().trim();
const cards = this.$el.querySelectorAll('.item-card');
let visibleCount = 0;
cards.forEach(card => {
const text = card.dataset.searchText || '';
const isVisible = searchLower === '' || text.includes(searchLower);
card.style.display = isVisible ? '' : 'none';
if (isVisible) visibleCount++;
});
this.hasResults = visibleCount > 0;
},
}
In the view, each card or row of data would carry an attribute data-search-text with the text already prepared in lowercaseTherefore, the filter is reduced to an includes() function in JavaScript, which is very fast for short lists:
<input type="search" x-model="search" x-ref="searchInput" placeholder="Buscar..." />
<div>
<div class="item-card" data-search-text="formulario contacto simple">
<h3>Formulario de contacto</h3>
<p>Formulario de contacto simple</p>
</div>
</div>
Additionally, you can display an empty state block only when There are no results for the current search term.inviting the user to modify the text or clear the field with a button that simply resets the search to an empty string.
This approach has clear advantages: There are no server calls during the search.The interaction is virtually instantaneous, and the logic remains highly local and easy to debug. It's perfect for quick selectors, item selection modals, or small catalogs embedded in a Laravel page.
Laravel Scout: Full-text search with specialized engines
When things get serious and you need fast, relevant, and scalable full-text searchesThe natural path in Laravel is Laravel Scout. Scout is an integration layer that allows you to easily connect your Eloquent models with search engines like Algolia, Meilisearch, your own database, in-memory collections, or even Elasticsearch through external controllers.
To get started with Scout, the usual thing is create a new Laravel project or reuse an existing oneTo launch it, use Docker (for example, with Laravel Sail) and then install the library with Composer. Once that's done, publish the scout.php configuration file and adjust the environment variables according to the driver you want to use.
A typical workflow would be to install Scout with Composer, publish its configuration, and activate the indexing queue with SCOUT_QUEUE=true In the .env file, ensure that resource-intensive operations are processed in the background, improving application response times. Additionally, you must ensure that DB_HOST points to the database you are using, which is especially important if you are using Docker containers.
In order for a model to participate in Scout searches, it is necessary to To explicitly mark it as searchable by adding the Searchable traitFor example, if you have a Train model that represents a table of trains with a title field, you could define it like this:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Train extends Model
{
use Searchable;
protected $fillable = ['title'];
public function searchableAs()
{
return 'trains_index';
}
}
The searchableAs method allows customize the index name in the search engineInstead of using the default name derived from the model, Scout takes over. From here, Scout handles synchronizing the creation, update, and deletion operations with the remote or local index, depending on the chosen driver.
Laravel Scout with Algolia: Lightning-fast SaaS Search
Algolia is a SaaS service focused on to offer very fast and relevant searches across large volumes of dataIt has a web panel for managing indexes, relevance rules, synonyms, etc., and integrates very well with Laravel through Scout and the official PHP client.
To use Algolia with Scout, you will need to install its PHP client with Composer, register your credentials in the .env file (Application ID and Admin API Key), and set SCOUT_DRIVER=algolia to tell Scout to use this engine. From the Algolia panel you can obtain both the application ID and the administrative key.
Once the environment is configured, you can use methods such as Train::search('text')->paginate(6) directly into your controllers to perform searches on the indexed fields, receiving results in paginated Eloquent format ready to be passed to a Blade view.
E.g.You could have a controller index that lists all trains or performs a search if a titlesearch parameter is received, and a create method to insert new trains into the index:
public function index(Request $request)
{
if ($request->has('titlesearch')) {
$trains = Train::search($request->titlesearch)->paginate(6);
} else {
$trains = Train::paginate(6);
}
return view('Train-search', compact('trains'));
}
public function create(Request $request)
{
$this->validate($request, ['title' => 'required']);
Train::create($request->all());
return back();
}
In the corresponding view, you can combine a form for registering new trains and another GET form with a titlesearch field that triggers the search upon submission. Then you just need to iterate through the collection of trains and display their fields in a table, taking advantage of the pagination links generated by Laravel.
Scout with Meilisearch, database and collections
If you prefer to avoid external services, Meilisearch is an open source search engine which you can deploy locally or on your infrastructure. Scout integrates with Meilisearch in a very similar way to Algolia, simply by changing the driver and adding the MEILISEARCH_HOST and MEILISEARCH_KEY variables to the .env file.
To use it, you install the Meilisearch PHP client, adjust SCOUT_DRIVER=meilisearch and point MEILISEARCH_HOST to the instance URL (for example, http://127.0.0.1:7700). If you already had previous records, you can index them with the command php artisan scout:import "App\Models\Train" so that the engine has them available.
In smaller or moderate applications, you can also choose the Scout driver databaseThis leverages full-text indexes and LIKE commands on your MySQL or PostgreSQL database. In this case, you don't need an external service; simply set SCOUT_DRIVER=database for Scout to use the database itself as its search engine.
Another interesting option is the driver collection, which works on Eloquent collections in memoryThis engine filters results using WHERE clauses and collection filtering, and is compatible with any database supported by Laravel. You can activate it with `SCOUT_DRIVER=collection` or by adjusting the Scout configuration file for more specific settings.
Integration with Elasticsearch using Explorer
If your search needs involve working with huge volumes of data and real-time analysisElasticsearch is a classic. In the Laravel ecosystem, a modern way to integrate it with Scout is to use the Explorer controller, which acts as a bridge between your models and an Elasticsearch cluster.
To do this, Docker is usually used, along with a rich docker-compose file that, in addition to the typical services (Laravel, MySQL, Redis, Meilisearch, etc.), Elasticsearch and Kibana containersThen you install the jeroen-g/explorer package via Composer and publish its configuration file to indicate which models should be indexed.
In the config/explorer.php file you can register your models under the indexes key, for example by adding App\Models\Train::classAdditionally, you change the Scout driver to elastic in the .env file with SCOUT_DRIVER=elastic so that everything points to Elasticsearch.
Within the Train model, the Explored interface must be implemented and the method overridden. mappableAswhich defines the map of fields that will be sent to the index. A minimal example would be:
use JeroenG\Explorer\Application\Explored;
use Laravel\Scout\Searchable;
class Train extends Model implements Explored
{
use Searchable;
protected $fillable = ['title'];
public function mappableAs(): array
{
return [
'id' => $this->id,
'title' => $this->title,
];
}
}
From now on, You can launch searches on Elasticsearch using the same Scout interface., benefiting from very low response times and the full query power of this engine, but without leaving the Laravel ecosystem.
With all these approaches—from basic autocomplete with fetch or jQuery, to frontend filtering with Alpine.js, to full-text searches with Laravel Scout and various drivers— Laravel gives you a huge range of options for implementing real-time searches tailored to the size of your project, the performance you need, and the infrastructure you are willing to maintain.
Table of Contents
- What is a real-time search in Laravel and how does the basics work?
- Model, routes, and controller for a basic real-time search engine
- Blade view and JavaScript fetch for reactive search
- Live search with Laravel and AJAX using jQuery
- Real-time filtering and search on the frontend with Alpine.js
- Laravel Scout: Full-text search with specialized engines
- Laravel Scout with Algolia: Lightning-fast SaaS Search
- Scout with Meilisearch, database and collections
- Integration with Elasticsearch using Explorer