- Laravel permite implementar desde buscadores simples con AJAX hasta búsquedas de texto completo avanzadas usando Laravel Scout y motores externos como Algolia, Meilisearch o Elasticsearch.
- Para búsquedas ligeras, el filtrado en el frontend con Alpine.js o con peticiones fetch nativas evita sobrecargar el servidor y mejora la experiencia de usuario en listas pequeñas.
- Laravel Scout centraliza la integración con distintos motores de búsqueda y facilita marcar modelos como buscables, gestionar índices y lanzar consultas de forma uniforme.
- La elección del motor (SaaS, open source o base de datos) debe basarse en el volumen de datos, la complejidad de las búsquedas y los requisitos de rendimiento y mantenimiento del proyecto.
Cuando empiezas a trabajar con Laravel y necesitas un buscador en tiempo real que responda al momento, es fácil perderse entre mil enfoques posibles: AJAX con fetch, jQuery, Alpine.js, Scout con Algolia o Meilisearch, filtrado en el frontend, etc. La buena noticia es que el ecosistema de Laravel ya trae prácticamente todo lo que necesitas para montar una búsqueda fluida y rápida sin morir en el intento.
En este artículo vas a ver cómo montar diferentes tipos de búsqueda en tiempo real en Laravel, desde el clásico autocompletado con AJAX hasta búsquedas de texto completo con Laravel Scout y motores como Algolia, Meilisearch, la propia base de datos o incluso Elasticsearch. Además, verás alternativas ligeras con Alpine.js para filtrar datos directamente en el navegador cuando el volumen de información es pequeño.
Qué es una búsqueda en tiempo real en Laravel y cómo funciona la base
La idea de una búsqueda en tiempo real es que, según el usuario va escribiendo en un campo de texto, se dispare una consulta y se actualicen los resultados sin recargar la página. A nivel técnico, esto implica tres piezas muy claras: el backend en Laravel, el JavaScript del navegador y el intercambio de datos en formato JSON.
Por un lado, Laravel actúa como la capa de servidor encargada de recibir las peticiones, interpretar los parámetros de búsqueda (el texto que se escribe), consultar la base de datos y devolver una respuesta estructurada, normalmente en JSON. Esta respuesta puede indicar éxito, error o que no se han encontrado resultados.
En el otro extremo, JavaScript se ocupa de escuchar los eventos del usuario sobre el input de búsqueda, lanzar peticiones asíncronas (AJAX) al backend y pintar en la página los datos devueltos, sin que el navegador haga un refresco completo. Esto se puede hacer con fetch nativo, con jQuery AJAX o con pequeñas librerías reactivas como Alpine.js.
Con este mecanismo básico puedes construir desde un autocompletado sencillo con unos pocos registros, hasta un buscador avanzado de texto completo con relevancia, paginación y filtros, apoyándote en librerías como Laravel Scout y motores externos optimizados para búsquedas.
Modelo, rutas y controlador para un buscador básico en tiempo real
Antes de meterte con JavaScript, necesitas que el lado de Laravel esté bien organizado: un modelo Eloquent sobre el que buscar, rutas claras y un controlador dedicado a gestionar la lógica de la búsqueda en tiempo real.
Lo primero es disponer de un modelo Eloquent que represente la tabla donde vas a buscar. Imagina una tabla de países y un modelo llamado Pais muy sencillo, sin marcas de tiempo y con asignación masiva permitida:
Ejemplo de modelo Eloquent mínimo para búsquedas:
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;
}
Aquí se indica que el modelo Pais se ubica en el espacio de nombres estándar de Laravel, hereda de Model y permite asignar cualquier campo con create() al dejar el array guarded vacío. Al desactivar los timestamps con public $timestamps = false, evitas problemas si la tabla no tiene las columnas created_at y updated_at.
El siguiente paso es definir las rutas que van a manejar tanto la visualización del buscador como las peticiones AJAX. Un esquema muy habitual combina una ruta GET para mostrar la vista y otra POST pensada para recibir las consultas en tiempo real:
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']);
La ruta raíz devuelve una vista de bienvenida, mientras que la URL /buscador se reserva para la funcionalidad de búsqueda. El método index() del controlador muestra el formulario y el input de búsqueda, mientras que el método buscar() procesa las peticiones asíncronas enviadas desde el navegador.
En el controlador puedes implementar un patrón muy práctico: preparar un array de respuesta por defecto en caso de error y sólo sobreescribirlo cuando efectivamente se trate de una petición AJAX válida y la consulta se ejecute sin problemas.
El controlador podría tener una estructura similar a esta:
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);
}
}
En este punto ya tienes el ciclo backend completo: petición AJAX entrante, comprobación de que es AJAX, consulta con where like y limitación de resultados a un número razonable con take(10) para no sobrecargar la base de datos. La respuesta se envía siempre en JSON, lo que simplifica mucho el trabajo del frontend.
Vista Blade y JavaScript fetch para una búsqueda reactiva
Con el modelo, las rutas y el controlador listos, toca construir la parte visible: un formulario con un campo de búsqueda y un bloque donde mostrar los resultados, más el JavaScript encargado de realizar las peticiones en segundo plano.
La vista Blade puede ser muy sencilla, apoyándose en el token CSRF que Laravel inyecta para validar las peticiones POST y en un input de tipo search que resulta cómodo de usar:
<!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>
En este ejemplo el script escucha el evento keyup sobre el input de búsqueda, de forma que cada pulsación dispara una petición fetch hacia la ruta /buscador. Se envía el texto actual del campo en formato JSON y se incluyen cabeceras clave como X-Requested-With para indicar que es AJAX y el token CSRF para pasar la protección nativa de Laravel.
Cuando llega la respuesta, se transforma en JSON y se genera dinámicamente un pequeño listado HTML con los resultados, o un mensaje tipo “No existen resultados” cuando la consulta no devuelve datos. Todo ello sin recargar la página, de forma natural para el usuario.
Este patrón se puede afinar aún más con pequeños detalles de UX, como añadir un retardo (debounce) entre pulsaciones, mostrar un loader o manejar errores de red para evitar que la interfaz parezca congelada cuando algo falla.
Búsqueda en vivo con Laravel y AJAX usando jQuery
Aunque hoy en día fetch ha ganado mucho terreno, jQuery AJAX sigue siendo muy popular en proyectos legacy o en equipos que ya lo tienen incorporado. La idea es exactamente la misma: capturar lo que el usuario escribe, hacer una petición asíncrona y refrescar el DOM.
Un flujo de trabajo típico con jQuery en Laravel para una búsqueda en vivo suele incluir estos pasos básicos: definir una ruta específica, crear un controlador dedicado, construir la vista Blade con el input de búsqueda y por último añadir el código jQuery que dispare AJAX según se teclea.
El proceso funciona así: cuando el usuario empieza a escribir, jQuery envía una consulta al servidor con la cadena de búsqueda. Laravel filtra la información en base de datos, devuelve un JSON con los resultados coincidentes y jQuery actualiza un contenedor HTML en la página para reflejar las coincidencias, todo en cuestión de milisegundos.
La ventaja de usar jQuery es que sintetiza bastante la sintaxis de AJAX y es muy directo de leer si ya tienes la librería en tu proyecto. A cambio, arrastras una dependencia adicional que quizás no sea necesaria si puedes trabajar con JavaScript moderno y fetch nativo.
Filtrado y búsqueda en tiempo real en el frontend con Alpine.js
Cuando los datos a mostrar son relativamente pocos (por ejemplo, menos de 50 elementos), no siempre compensa montar un backend con búsquedas complejas. En esos casos, una opción muy cómoda es filtrar directamente en el navegador con Alpine.js, sin hacer peticiones al servidor mientras el usuario escribe.
La idea consiste en precalcular una cadena de búsqueda para cada elemento (por ejemplo, nombre, descripción y categoría en minúsculas), guardarla en un atributo data-search-text y dejar que Alpine.js se encargue de mostrar u ocultar los elementos según el texto escrito en un campo de búsqueda.
El componente Alpine.js puede tener una estructura parecida a esta: 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;
},
}
En la vista, cada tarjeta o fila de datos llevaría un atributo data-search-text con el texto ya preparado en minúsculas, de manera que el filtro se reduce a un includes() en JavaScript, muy rápido para listas cortas:
<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>
Además, puedes mostrar un bloque de estado vacío sólo cuando no hay resultados para el término actual de búsqueda, invitando al usuario a modificar el texto o limpiar el campo con un botón que simplemente reinicia search a cadena vacía.
Este enfoque tiene ventajas claras: no hay llamadas al servidor durante la búsqueda, la interacción es prácticamente instantánea y la lógica se mantiene muy local y fácil de depurar. Es perfecto para selectores rápidos, modales de elección de elementos o pequeños catálogos incrustados en una página de Laravel.
Laravel Scout: búsqueda de texto completo con motores especializados
Cuando la cosa se pone seria y necesitas búsquedas de texto completo rápidas, con relevancia y escalables, el camino natural en Laravel es Laravel Scout. Scout es una capa de integración que permite conectar de forma sencilla tus modelos Eloquent con motores de búsqueda como Algolia, Meilisearch, la propia base de datos, colecciones en memoria o incluso Elasticsearch a través de controladores externos.
Para empezar con Scout, lo habitual es crear un nuevo proyecto Laravel o reutilizar uno existente, lanzarlo con Docker (por ejemplo, usando Laravel Sail) y después instalar la librería con Composer. Una vez hecho esto, publicas el archivo de configuración scout.php y ajustas las variables de entorno según el driver que quieras usar.
Un flujo típico sería instalar Scout con Composer, publicar su configuración y activar la cola de indexación con SCOUT_QUEUE=true en el archivo .env para que las operaciones pesadas se procesen en segundo plano, mejorando los tiempos de respuesta de la aplicación. Además, debes asegurarte de que DB_HOST apunte a la base de datos que estés usando, algo especialmente importante si estás dentro de contenedores Docker.
Para que un modelo pueda participar en las búsquedas de Scout, hay que marcarlo explícitamente como buscable añadiendo el trait Searchable. Por ejemplo, si tienes un modelo Train que representa una tabla de trenes con un campo title, podrías definirlo así:
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';
}
}
El método searchableAs permite personalizar el nombre del índice en el motor de búsqueda, en lugar de usar el nombre por defecto derivado del modelo. A partir de aquí, Scout se encarga de sincronizar las operaciones de creación, actualización y borrado con el índice remoto o local, según el driver elegido.
Laravel Scout con Algolia: búsqueda SaaS ultrarrápida
Algolia es un servicio SaaS centrado en ofrecer búsquedas muy rápidas y relevantes sobre grandes volúmenes de datos. Dispone de un panel web para gestionar índices, reglas de relevancia, sinónimos, etc., y se integra muy bien con Laravel a través de Scout y del cliente PHP oficial.
Para usar Algolia con Scout tendrás que instalar su cliente PHP con Composer, registrar tus credenciales en el archivo .env (Application ID y Admin API Key) y configurar SCOUT_DRIVER=algolia para indicarle a Scout que use este motor. Desde el panel de Algolia puedes obtener tanto el ID de la aplicación como la clave administrativa.
Una vez configurado el entorno, ya puedes usar métodos como Train::search(‘texto’)->paginate(6) directamente en tus controladores para realizar búsquedas sobre los campos indexados, recibiendo resultados en formato Eloquent paginado listo para pasar a una vista Blade.
Por ejemplo, podrías tener un controlador index que liste todos los trenes o aplique búsqueda si llega un parámetro titlesearch, y un método create para insertar nuevos trenes en el índice:
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();
}
En la vista correspondiente, puedes combinar un formulario para alta de nuevos trenes y otro formulario GET con un campo titlesearch que dispare la búsqueda al enviar. Luego sólo tienes que recorrer la colección de trenes y mostrar sus campos en una tabla, aprovechando los enlaces de paginación generados por Laravel.
Scout con Meilisearch, base de datos y colecciones
Si prefieres evitar servicios externos, Meilisearch es un motor de búsqueda open source que puedes desplegar en local o en tu infraestructura. Scout se integra con Meilisearch de manera muy similar a Algolia, cambiando simplemente el driver y añadiendo las variables MEILISEARCH_HOST y MEILISEARCH_KEY en el .env.
Para usarlo, instalas el cliente PHP de Meilisearch, ajustas SCOUT_DRIVER=meilisearch y apuntas MEILISEARCH_HOST a la URL de la instancia (por ejemplo, http://127.0.0.1:7700). Si ya tenías registros previos, puedes indexarlos con el comando php artisan scout:import «App\Models\Train» para que el motor los tenga disponibles.
En aplicaciones más pequeñas o moderadas, también puedes elegir el driver database de Scout, que aprovecha índices de texto completo e instrucciones like sobre tu base de datos MySQL o PostgreSQL. En este caso no necesitas un servicio externo, basta con establecer SCOUT_DRIVER=database para que Scout use la propia base de datos como motor de búsqueda.
Otra opción interesante es el driver collection, que trabaja sobre colecciones Eloquent en memoria. Este motor filtra los resultados con métodos where y filtrado de colecciones, y es compatible con cualquier base de datos soportada por Laravel. Puedes activarlo con SCOUT_DRIVER=collection o ajustando el archivo de configuración de Scout si quieres algo más específico.
Integración con Elasticsearch mediante Explorer
Si tus necesidades de búsqueda pasan por trabajar con volúmenes enormes de datos y análisis en tiempo real, Elasticsearch es un clásico. En el ecosistema de Laravel, una forma moderna de integrarlo con Scout es usar el controlador Explorer, que actúa como puente entre tus modelos y un clúster de Elasticsearch.
Para ello, se suele trabajar con Docker y un archivo docker-compose enriquecido que levanta, además de los servicios típicos (Laravel, MySQL, Redis, Meilisearch, etc.), contenedores de Elasticsearch y Kibana. Después instalas el paquete jeroen-g/explorer vía Composer y publicas su archivo de configuración para indicar qué modelos deben indexarse.
En el archivo config/explorer.php puedes registrar tus modelos en la clave indexes, por ejemplo añadiendo App\Models\Train::class. Además, cambias el driver de Scout a elastic en el archivo .env con SCOUT_DRIVER=elastic para que todo apunte a Elasticsearch.
Dentro del modelo Train hay que implementar la interfaz Explored y sobrescribir el método mappableAs, que define el mapa de campos que se enviarán al índice. Un ejemplo mínimo sería:
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,
];
}
}
A partir de aquí, puedes lanzar búsquedas sobre Elasticsearch usando la misma interfaz de Scout, beneficiándote de tiempos de respuesta muy bajos y de toda la potencia de consultas de este motor, pero sin salir del ecosistema Laravel.
Con todos estos enfoques —desde el autocompletado básico con fetch o jQuery, pasando por filtrado en el frontend con Alpine.js, hasta búsquedas de texto completo con Laravel Scout y distintos drivers— Laravel te da un abanico enorme de opciones para implementar búsquedas en tiempo real adaptadas al tamaño de tu proyecto, al rendimiento que necesitas y a la infraestructura que estés dispuesto a mantener.
Tabla de Contenidos
- Qué es una búsqueda en tiempo real en Laravel y cómo funciona la base
- Modelo, rutas y controlador para un buscador básico en tiempo real
- Vista Blade y JavaScript fetch para una búsqueda reactiva
- Búsqueda en vivo con Laravel y AJAX usando jQuery
- Filtrado y búsqueda en tiempo real en el frontend con Alpine.js
- Laravel Scout: búsqueda de texto completo con motores especializados
- Laravel Scout con Algolia: búsqueda SaaS ultrarrápida
- Scout con Meilisearch, base de datos y colecciones
- Integración con Elasticsearch mediante Explorer