Complete guide to real-time search in Laravel

Last update: December 5th 2025
  • 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.

real-time search in Laravel

When you start working with Laravel and need a real-time search engine that responds instantly , it'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 build a smooth and fast search engine without getting overwhelmed.

In this article, you'll learn how to implement different types of real-time search in Laravel , from classic AJAX autocomplete to full-text searches using Laravel Scout and search engines like Algolia, Meilisearch, the database itself, or even Elasticsearch. You'll also explore lightweight alternatives with Alpine.js for filtering data directly in the browser when dealing with small datasets.

What is a real-time search in Laravel and how does the basics work?

The idea behind real-time search is that, as the user types in a text field , a 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 data exchange in JSON format.

On one hand, Laravel acts as the server layer 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 handles listening for user events on the search input, sending asynchronous requests (AJAX) to the backend, and rendering the returned data on the page without requiring a full browser refresh. This can be done with native fetch, jQuery AJAX, or small reactive libraries like Alpine.js.

With this basic mechanism you can build anything from a simple autocomplete with a few records , to an advanced full-text search engine with relevance, pagination and filters, relying on libraries like Laravel Scout and external search engines optimized for searches.

Model, routes, and controller for a basic real-time search engine

Before you delve into JavaScript, you need the Laravel side to be well organized: an Eloquent model to search on, clear routes, and a controller dedicated to handling 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 very simple model called Country , 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;
}

This indicates that the Pais model is located in the standard Laravel namespace , inherits from Model, and allows assigning 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 AJAX requests . A very common scheme combines a GET route to display the view and a POST route designed to receive queries in real time:

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 /search URL is reserved for search functionality . The 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 actually a valid AJAX request and the query executes without problems.

The controller could have a structure similar to 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 have the complete backend cycle: incoming AJAX request, verification that it is indeed AJAX, query using `where like`, and limiting the number of results to a reasonable amount with `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 the requests in the background.

The Blade view can be very simple, relying on the CSRF token that Laravel injects to validate POST requests and on 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 listens for the keyup event on the search input , so each 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's AJAX, along with the CSRF token to bypass Laravel's native protection.

When the response arrives, it is transformed into JSON and a small HTML list with the results is dynamically generated , or a message such as "No results found" is displayed if the query returns no data. All of this happens 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 debounce between keystrokes, displaying a loader, or handling network errors to prevent the interface from appearing frozen when something fails.

Live search with Laravel and AJAX using jQuery

Although fetch has gained significant traction, jQuery AJAX remains very popular in legacy projects or teams that already have it integrated. 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 a 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 object 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 greatly simplifies AJAX syntax and is very straightforward to read if you already have the library in your project. However, it introduces an additional dependency that might not be necessary if you can work with modern JavaScript and native fetch functionality.

Real-time filtering and search on the frontend with Alpine.js

When the data to be displayed is relatively small (for example, fewer than 50 items ), it's not always worthwhile to set up a backend with complex searches. In these cases, a very convenient option is to filter directly in the browser using Alpine.js , without making requests to the server while the user types.

The idea is to pre-calculate a search string for each item (e.g., name, description, and category in lowercase), save it in a data-search-text attribute, and let Alpine.js handle showing or hiding items based on the text entered 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 have a data-search-text attribute with the text already prepared in lowercase , so the filter is reduced to an includes() in JavaScript, 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 status 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 , 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 search , the 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 database, in-memory collections, or even Elasticsearch via external controllers.

To get started with Scout, the usual approach is to create a new Laravel project or reuse an existing one , launch it with Docker (for example, using Laravel Sail), and then install the library with Composer. Once that's done, you 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 enable the indexing queue with `SCOUT_QUEUE=true` in the `.env` file so that resource-intensive operations are processed in the background, improving application response times. Additionally, you should ensure that `DB_HOST` points to the database you are using, which is especially important if you are using Docker containers.

For a model to participate in Scout searches, it must be explicitly marked as searchable by adding the `Searchable` trait . For 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 you to customize the index name in the search engine , instead of using the default name derived from the model. From there, Scout handles synchronizing 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 providing very fast and relevant searches across large volumes of data . It features a web panel for managing indexes, relevance rules, synonyms, and more, and integrates seamlessly with Laravel via Scout and the official PHP client.

To use Algolia with Scout, you'll need to install its PHP client using Composer, register your credentials in the .env file (Application ID and Admin API Key), and configure SCOUT_DRIVER=algolia to tell Scout to use this engine. You can obtain both the application ID and the admin key from the Algolia dashboard.

Once the environment is set up, you can use methods like Train::search('text')->paginate(6) directly in your controllers to perform searches on the indexed fields, receiving results in paginated Eloquent format ready to be passed to a Blade view.

For example , you could have an index controller that lists all trains or performs a search if a titlesearch parameter is passed, 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 adding new trains and another GET form with a title search field that triggers the search upon submission. Then you simply 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 that 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, install the Meilisearch PHP client, set SCOUT_DRIVER=meilisearch , and point MEILISEARCH_HOST to the instance URL (for example, http://127.0.0.1:7700). If you already have records, you can index them with the command php artisan scout:import "App\Models\Train" so that the engine has them available.

For smaller or moderately sized applications, you can also choose Scout's database driver , which leverages full-text indexes and LIKE statements 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 collection driver, which works with in-memory Eloquent collections . This engine filters results using `where` and collection filtering methods, and it's compatible with any database supported by Laravel. You can activate it with `SCOUT_DRIVER=collection` or by adjusting the Scout configuration file if you want something more specific.

Integration with Elasticsearch using Explorer

If your search needs involve working with massive volumes of data and real-time analytics , Elasticsearch 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.

This typically involves working with Docker and a rich docker-compose file that launches, in addition to the usual services (Laravel, MySQL, Redis, Meilisearch, etc.), Elasticsearch and Kibana containers . Then you install the jeroen-g/explorer package via Composer and publish its configuration file to specify 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::class . Additionally, you change the driver from Scout to Elasticsearch in the .env file with SCOUT_DRIVER=elastic so that everything points to Elasticsearch.

Within the Train model, you need to implement the Explored interface and override the mappableAs method , which defines the map of fields to 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 here, 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 to implement real-time searches tailored to the size of your project, the performance you need, and the infrastructure you're willing to maintain.

  How to transform your PC into a real AI lab