- O Laravel permite implementar desde mecanismos de busca simples com AJAX até buscas avançadas de texto completo usando o Laravel Scout e mecanismos de busca externos como Algolia, Meilisearch ou Elasticsearch.
- Para buscas leves, filtrar no frontend com Alpine.js ou com requisições fetch nativas evita sobrecarregar o servidor e melhora a experiência do usuário em listas pequenas.
- O Laravel Scout centraliza a integração com diferentes mecanismos de busca e facilita a marcação de modelos como pesquisáveis, o gerenciamento de índices e o lançamento uniforme de consultas.
- A escolha do mecanismo de busca (SaaS, código aberto ou banco de dados) deve ser baseada no volume de dados, na complexidade das buscas e nos requisitos de desempenho e manutenção do projeto.
Ao começar a trabalhar com Laravel e precisar de um mecanismo de busca em tempo real com resposta instantânea , é fácil se perder em meio a milhares de abordagens possíveis: AJAX com fetch, jQuery, Alpine.js, Scout com Algolia ou Meilisearch, filtragem no frontend, etc. A boa notícia é que o ecossistema Laravel já oferece praticamente tudo o que você precisa para construir um mecanismo de busca ágil e eficiente, sem se sentir sobrecarregado.
Neste artigo, você aprenderá como implementar diferentes tipos de busca em tempo real no Laravel , desde o clássico autocompletar com AJAX até buscas de texto completo usando o Laravel Scout e mecanismos de busca como Algolia, Meilisearch, o próprio banco de dados ou até mesmo o Elasticsearch. Você também explorará alternativas leves com Alpine.js para filtrar dados diretamente no navegador ao lidar com conjuntos de dados pequenos.
O que é uma busca em tempo real no Laravel e como funciona basicamente?
A ideia por trás da busca em tempo real é que, à medida que o usuário digita em um campo de texto , uma consulta é acionada e os resultados são atualizados sem recarregar a página. Tecnicamente, isso envolve três componentes principais: o backend Laravel, o JavaScript do navegador e a troca de dados em formato JSON.
Por um lado, o Laravel atua como a camada de servidor responsável por receber as requisições, interpretar os parâmetros de busca (o texto digitado), consultar o banco de dados e retornar uma resposta estruturada, geralmente em formato JSON. Essa resposta pode indicar sucesso, erro ou que nenhum resultado foi encontrado.
Na outra ponta, o JavaScript se encarrega de ouvir os eventos do usuário no campo de busca, enviar requisições assíncronas (AJAX) para o servidor e renderizar os dados retornados na página sem precisar atualizar completamente o navegador. Isso pode ser feito com fetch nativo, jQuery AJAX ou pequenas bibliotecas reativas como Alpine.js.
Com esse mecanismo básico, você pode construir desde um simples recurso de autocompletar com alguns registros até um mecanismo de busca de texto completo avançado com relevância, paginação e filtros, utilizando bibliotecas como o Laravel Scout e mecanismos de busca externos otimizados para pesquisas.
Modelo, rotas e controlador para um mecanismo de busca básico em tempo real.
Antes de se aprofundar no JavaScript, você precisa que o lado do Laravel esteja bem organizado: um modelo Eloquent para pesquisa, rotas claras e um controlador dedicado a lidar com a lógica de pesquisa em tempo real.
O primeiro passo é ter um modelo Eloquent que represente a tabela onde você vai pesquisar. Imagine uma tabela de países e um modelo muito simples chamado `Country` , sem registros de data e hora e com atribuição em massa permitida:
Exemplo de um modelo Eloquent mínimo para buscas :
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;
}
Isso indica que o modelo Pais está localizado no namespace padrão do Laravel , herda de Model e permite atribuir qualquer valor a um campo com create() deixando o array protegido vazio. Ao desabilitar os timestamps com public $timestamps = false, você evita problemas caso a tabela não possua as colunas created_at e updated_at.
O próximo passo é definir as rotas que irão lidar tanto com a exibição no mecanismo de busca quanto com as requisições AJAX . Um esquema muito comum combina uma rota GET para exibir a página e uma rota POST projetada para receber consultas em tempo 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']);
A rota raiz retorna uma tela de boas-vindas, enquanto a URL /search é reservada para a funcionalidade de busca . O método index() do controlador exibe o formulário e o campo de busca, enquanto o método search() processa as requisições assíncronas enviadas pelo navegador.
No controlador, você pode implementar um padrão muito prático: preparar um array de resposta padrão para o caso de erro e sobrescrevê-lo somente quando se tratar de uma requisição AJAX válida e a consulta for executada sem problemas.
O controlador poderia ter uma estrutura semelhante 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);
}
}
Neste ponto, você tem o ciclo completo do backend: requisição AJAX recebida, verificação de que é realmente uma requisição AJAX, consulta usando `where like` e limitação do número de resultados a uma quantidade razoável com `take(10)` para evitar sobrecarga do banco de dados. A resposta é sempre enviada em JSON, o que simplifica bastante o trabalho do frontend.
Blade view e JavaScript fetch para pesquisa reativa
Com o modelo, as rotas e o controlador prontos, é hora de construir a parte visível: um formulário com um campo de pesquisa e um bloco para exibir os resultados , além do JavaScript responsável por fazer as requisições em segundo plano.
A interface Blade pode ser muito simples, dependendo do token CSRF que o Laravel injeta para validar as requisições POST e de um campo de busca de fácil utilização:
<!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>
Neste exemplo, o script fica à escuta do evento keyup no campo de pesquisa , de forma que cada tecla pressionada aciona uma requisição fetch para o caminho /search. O texto atual do campo é enviado em formato JSON, e cabeçalhos importantes como X-Requested-With são incluídos para indicar que se trata de uma requisição AJAX, juntamente com o token CSRF para contornar a proteção nativa do Laravel.
Quando a resposta chega, ela é transformada em JSON e uma pequena lista HTML com os resultados é gerada dinamicamente , ou uma mensagem como "Nenhum resultado encontrado" é exibida se a consulta não retornar dados. Tudo isso acontece sem recarregar a página, de forma natural para o usuário.
Esse padrão pode ser ainda mais refinado com pequenos detalhes de UX, como adicionar um debounce entre as teclas pressionadas, exibir um indicador de carregamento ou lidar com erros de rede para evitar que a interface pareça congelada quando algo falhar.
Pesquisa em tempo real com Laravel e AJAX usando jQuery
Embora o fetch tenha ganhado bastante popularidade, o jQuery AJAX continua sendo muito utilizado em projetos legados ou em equipes que já o integraram. A ideia é exatamente a mesma: capturar o que o usuário digita, fazer uma requisição assíncrona e atualizar o DOM.
Um fluxo de trabalho típico com jQuery no Laravel para uma pesquisa em tempo real geralmente inclui estas etapas básicas: definir uma rota específica, criar um controlador dedicado, construir a view Blade com o campo de entrada de pesquisa e, finalmente, adicionar o código jQuery que dispara o AJAX conforme o texto é digitado.
O processo funciona assim: quando o usuário começa a digitar, o jQuery envia uma consulta ao servidor com a string de busca. O Laravel filtra as informações no banco de dados, retorna um objeto JSON com os resultados correspondentes e o jQuery atualiza um contêiner HTML na página para refletir as correspondências, tudo em questão de milissegundos.
A vantagem de usar jQuery é que ele simplifica bastante a sintaxe AJAX e é muito fácil de entender se você já tiver a biblioteca em seu projeto. No entanto, ele introduz uma dependência adicional que pode não ser necessária se você souber trabalhar com JavaScript moderno e a funcionalidade nativa de busca (fetch).
Filtragem e busca em tempo real no frontend com Alpine.js
Quando a quantidade de dados a serem exibidos é relativamente pequena (por exemplo, menos de 50 itens ), nem sempre compensa configurar um backend com buscas complexas. Nesses casos, uma opção muito conveniente é filtrar diretamente no navegador usando Alpine.js , sem fazer requisições ao servidor enquanto o usuário digita.
A ideia é pré-calcular uma string de busca para cada item (por exemplo, nome, descrição e categoria em letras minúsculas), salvá-la em um atributo data-search-text e deixar que o Alpine.js mostre ou oculte os itens com base no texto inserido no campo de busca.
O componente Alpine.js pode ter uma estrutura semelhante 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;
},
}
Na visualização, cada cartão ou linha de dados teria um atributo data-search-text com o texto já preparado em letras minúsculas , de modo que o filtro se reduz a um includes() em JavaScript, muito rápido para listas curtas:
<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>
Além disso, você pode exibir um bloco de status vazio somente quando não houver resultados para o termo de pesquisa atual , convidando o usuário a modificar o texto ou limpar o campo com um botão que simplesmente redefine a pesquisa para uma string vazia.
Essa abordagem tem vantagens claras: não há chamadas ao servidor durante a busca , a interação é praticamente instantânea e a lógica permanece altamente local e fácil de depurar. É perfeita para seletores rápidos, modais de seleção de itens ou pequenos catálogos incorporados em uma página Laravel.
Laravel Scout: Busca de texto completo com mecanismos especializados
Quando a situação fica séria e você precisa de uma busca de texto completo rápida, relevante e escalável , o caminho natural no Laravel é o Laravel Scout. O Scout é uma camada de integração que permite conectar facilmente seus modelos Eloquent com mecanismos de busca como Algolia, Meilisearch, seu banco de dados, coleções em memória ou até mesmo o Elasticsearch por meio de controladores externos.
Para começar a usar o Scout, a abordagem usual é criar um novo projeto Laravel ou reutilizar um existente , executá-lo com o Docker (por exemplo, usando o Laravel Sail) e, em seguida, instalar a biblioteca com o Composer. Feito isso, você publica o arquivo de configuração scout.php e ajusta as variáveis de ambiente de acordo com o driver que deseja usar.
Um fluxo de trabalho típico seria instalar o Scout com o Composer, publicar sua configuração e habilitar a fila de indexação com `SCOUT_QUEUE=true` no arquivo `.env`, para que as operações que consomem muitos recursos sejam processadas em segundo plano, melhorando os tempos de resposta do aplicativo. Além disso, você deve garantir que `DB_HOST` aponte para o banco de dados que você está usando, o que é especialmente importante se você estiver usando contêineres Docker.
Para que um modelo participe das buscas do Scout, ele deve ser explicitamente marcado como pesquisável adicionando o trait `Searchable` . Por exemplo, se você tiver um modelo `Train` que representa uma tabela de trens com um campo `title`, você poderia defini-lo assim:
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';
}
}
O método `searchableAs` permite personalizar o nome do índice no mecanismo de busca , em vez de usar o nome padrão derivado do modelo. A partir daí, o Scout gerencia a sincronização das operações de criação, atualização e exclusão com o índice remoto ou local, dependendo do driver escolhido.
Laravel Scout com Algolia: Busca SaaS ultrarrápida
Algolia é um serviço SaaS focado em fornecer buscas rápidas e relevantes em grandes volumes de dados . Possui um painel web para gerenciar índices, regras de relevância, sinônimos e muito mais, e se integra perfeitamente ao Laravel via Scout e o cliente PHP oficial.
Para usar o Algolia com o Scout, você precisará instalar o cliente PHP usando o Composer, registrar suas credenciais no arquivo .env (ID do aplicativo e chave da API de administrador) e configurar SCOUT_DRIVER=algolia para que o Scout use esse mecanismo. Você pode obter o ID do aplicativo e a chave de administrador no painel do Algolia.
Uma vez que o ambiente esteja configurado, você pode usar métodos como Train::search('text')->paginate(6) diretamente em seus controladores para realizar pesquisas nos campos indexados, recebendo resultados em formato Eloquent paginado, prontos para serem passados para uma view Blade.
Por exemplo , você poderia ter um controlador de índice que lista todos os trens ou realiza uma busca se um parâmetro de pesquisa de título for passado, e um método de criação para inserir novos trens no í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();
}
Na visualização correspondente, você pode combinar um formulário para adicionar novos trens e outro formulário GET com um campo de busca por título que aciona a busca ao enviar o formulário. Em seguida, basta iterar pela coleção de trens e exibir seus campos em uma tabela, aproveitando os links de paginação gerados pelo Laravel.
Explorador com Meilisearch, banco de dados e coleções
Se preferir evitar serviços externos, o Meilisearch é um mecanismo de busca de código aberto que você pode implantar localmente ou em sua infraestrutura. O Scout se integra ao Meilisearch de maneira muito semelhante ao Algolia, bastando alterar o driver e adicionar as variáveis MEILISEARCH_HOST e MEILISEARCH_KEY ao arquivo .env.
Para utilizá-lo, instale o cliente PHP Meilisearch, defina SCOUT_DRIVER=meilisearch e aponte MEILISEARCH_HOST para a URL da instância (por exemplo, http://127.0.0.1:7700). Se você já possui registros, pode indexá-los com o comando php artisan scout:import "App\Models\Train" para que o mecanismo os tenha disponíveis.
Para aplicações de pequeno ou médio porte, você também pode optar pelo driver de banco de dados do Scout , que utiliza índices de texto completo e instruções LIKE em seu banco de dados MySQL ou PostgreSQL. Nesse caso, você não precisa de um serviço externo; basta definir SCOUT_DRIVER=database para que o Scout utilize o próprio banco de dados como mecanismo de busca.
Outra opção interessante é o driver de coleção, que funciona com coleções Eloquent em memória . Esse mecanismo filtra os resultados usando `where` e métodos de filtragem de coleção, e é compatível com qualquer banco de dados suportado pelo Laravel. Você pode ativá-lo com `SCOUT_DRIVER=collection` ou ajustando o arquivo de configuração do Scout, caso deseje algo mais específico.
Integração com o Elasticsearch usando o Explorer
Se suas necessidades de busca envolvem trabalhar com grandes volumes de dados e análises em tempo real , o Elasticsearch é uma solução clássica. No ecossistema Laravel, uma maneira moderna de integrá-lo ao Scout é usar o controlador Explorer, que atua como uma ponte entre seus modelos e um cluster Elasticsearch.
Normalmente, isso envolve o uso do Docker e um arquivo docker-compose robusto que inicia, além dos serviços usuais (Laravel, MySQL, Redis, Meilisearch etc.), os contêineres do Elasticsearch e do Kibana . Em seguida, você instala o pacote jeroen-g/explorer via Composer e publica seu arquivo de configuração para especificar quais modelos devem ser indexados.
No arquivo config/explorer.php, você pode registrar seus modelos na chave indexes, por exemplo, adicionando App\Models\Train::class . Além disso, você pode alterar o driver de Scout para Elasticsearch no arquivo .env com SCOUT_DRIVER=elastic para que tudo aponte para o Elasticsearch.
Dentro do modelo Train, você precisa implementar a interface Explored e sobrescrever o método mappableAs , que define o mapeamento de campos a serem enviados para o índice. Um exemplo mínimo seria:
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 daqui, você pode iniciar buscas no Elasticsearch usando a mesma interface do Scout , aproveitando tempos de resposta muito baixos e todo o poder de consulta desse mecanismo, mas sem sair do ecossistema Laravel.
Com todas essas abordagens — desde o preenchimento automático básico com fetch ou jQuery, até a filtragem no frontend com Alpine.js, passando por buscas de texto completo com Laravel Scout e diversos drivers — o Laravel oferece uma enorme variedade de opções para implementar buscas em tempo real, adaptadas ao tamanho do seu projeto, ao desempenho necessário e à infraestrutura que você está disposto a manter.