- Laravel consente di implementare di tutto, dai semplici motori di ricerca con AJAX alle ricerche avanzate full-text utilizzando Laravel Scout e motori di ricerca esterni come Algolia, Meilisearch o Elasticsearch.
- Per ricerche leggere, il filtraggio sul frontend con Alpine.js o con richieste di recupero native evita di sovraccaricare il server e migliora l'esperienza utente in elenchi di piccole dimensioni.
- Laravel Scout centralizza l'integrazione con diversi motori di ricerca e semplifica la marcatura dei modelli come ricercabili, la gestione degli indici e l'avvio di query in modo uniforme.
- La scelta del motore (SaaS, open source o database) dovrebbe basarsi sul volume dei dati, sulla complessità delle ricerche e sui requisiti di prestazioni e manutenzione del progetto.
Quando si inizia a lavorare con Laravel e si ha bisogno di un motore di ricerca in tempo reale che risponda istantaneamente , è facile perdersi tra mille possibili approcci: AJAX con fetch, jQuery, Alpine.js, Scout con Algolia o Meilisearch, filtri lato frontend, ecc. La buona notizia è che l'ecosistema di Laravel offre già praticamente tutto il necessario per costruire un motore di ricerca fluido e veloce senza sentirsi sopraffatti.
In questo articolo, imparerai come implementare diversi tipi di ricerca in tempo reale in Laravel , dal classico completamento automatico AJAX alle ricerche full-text utilizzando Laravel Scout e motori di ricerca come Algolia, Meilisearch, il database stesso o persino Elasticsearch. Esplorerai anche alternative leggere con Alpine.js per filtrare i dati direttamente nel browser quando si lavora con piccoli set di dati.
Cos'è una ricerca in tempo reale in Laravel e come funziona?
L'idea alla base della ricerca in tempo reale è che, non appena l'utente digita in un campo di testo , viene attivata una query e i risultati vengono aggiornati senza ricaricare la pagina. Tecnicamente, ciò coinvolge tre componenti chiave: il backend di Laravel, il JavaScript del browser e lo scambio di dati in formato JSON.
Da un lato, Laravel funge da livello server responsabile della ricezione delle richieste, dell'interpretazione dei parametri di ricerca (il testo inserito), dell'interrogazione del database e della restituzione di una risposta strutturata, solitamente in formato JSON. Questa risposta può indicare successo, errore o che non sono stati trovati risultati.
Dall'altro lato, JavaScript si occupa di intercettare gli eventi utente sul campo di ricerca, inviare richieste asincrone (AJAX) al backend e visualizzare i dati restituiti sulla pagina senza richiedere un aggiornamento completo del browser. Questo può essere fatto con fetch nativo, jQuery AJAX o piccole librerie reattive come Alpine.js.
Con questo meccanismo di base è possibile realizzare qualsiasi cosa, da un semplice completamento automatico con pochi record a un motore di ricerca full-text avanzato con funzionalità di pertinenza, paginazione e filtri, avvalendosi di librerie come Laravel Scout e di motori di ricerca esterni ottimizzati per le ricerche.
Modello, percorsi e controller per un motore di ricerca di base in tempo reale
Prima di addentrarti in JavaScript, è necessario che la parte Laravel sia ben organizzata: un modello Eloquent per la ricerca, percorsi chiari e un controller dedicato alla gestione della logica di ricerca in tempo reale.
Il primo passo consiste nell'avere un modello Eloquent che rappresenti la tabella in cui si intende effettuare la ricerca. Immaginate una tabella di paesi e un modello molto semplice chiamato Paese , senza timestamp e con assegnazione in blocco consentita:
Esempio di un modello Eloquent minimale per le ricerche :
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;
}
Questo indica che il modello Pais si trova nello spazio dei nomi standard di Laravel , eredita da Model e consente di assegnare qualsiasi campo con create() lasciando vuoto l'array protetto. Disabilitando i timestamp con public $timestamps = false, si evitano problemi se la tabella non ha le colonne created_at e updated_at.
Il passo successivo consiste nel definire i percorsi che gestiranno sia la visualizzazione del motore di ricerca sia le richieste AJAX . Uno schema molto comune combina un percorso GET per visualizzare la vista e un percorso POST progettato per ricevere le query in tempo reale:
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 route principale restituisce una schermata di benvenuto, mentre l' URL /search è riservato alla funzionalità di ricerca . Il metodo index() del controller visualizza il modulo e il campo di ricerca, mentre il metodo search() elabora le richieste asincrone inviate dal browser.
Nel controller è possibile implementare uno schema molto pratico: preparare un array di risposta predefinito in caso di errore e sovrascriverlo solo quando si tratta effettivamente di una richiesta AJAX valida e la query viene eseguita senza problemi.
Il controller potrebbe avere una struttura simile a questa:
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);
}
}
A questo punto, il ciclo di backend è completo: richiesta AJAX in arrivo, verifica che si tratti effettivamente di una richiesta AJAX, query tramite `where like` e limitazione del numero di risultati a una quantità ragionevole con `take(10)` per evitare di sovraccaricare il database. La risposta viene sempre inviata in formato JSON, il che semplifica notevolmente il lavoro del frontend.
Visualizzazione della lama e recupero JavaScript per la ricerca reattiva
Con il modello, le route e il controller pronti, è il momento di costruire la parte visibile: un modulo con un campo di ricerca e un blocco per visualizzare i risultati , oltre al codice JavaScript responsabile dell'esecuzione delle richieste in background.
La vista Blade può essere molto semplice, basandosi sul token CSRF che Laravel inietta per convalidare le richieste POST e su un campo di ricerca di facile utilizzo:
<!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 questo esempio, lo script si mette in ascolto dell'evento keyup sul campo di input search , in modo che ogni pressione di un tasto attivi una richiesta fetch al percorso /search. Il testo corrente del campo viene inviato in formato JSON e sono incluse intestazioni chiave come X-Requested-With per indicare che si tratta di una richiesta AJAX, insieme al token CSRF per aggirare la protezione nativa di Laravel.
Quando arriva la risposta, viene trasformata in formato JSON e viene generato dinamicamente un piccolo elenco HTML con i risultati , oppure viene visualizzato un messaggio come "Nessun risultato trovato" se la query non restituisce dati. Tutto ciò avviene senza ricaricare la pagina, in modo naturale per l'utente.
Questo schema può essere ulteriormente perfezionato con piccoli dettagli di esperienza utente, come l'aggiunta di un effetto di debounce tra la pressione dei tasti, la visualizzazione di un indicatore di caricamento o la gestione degli errori di rete per evitare che l'interfaccia si blocchi in caso di problemi.
Ricerca live con Laravel e AJAX utilizzando jQuery
Sebbene fetch abbia guadagnato una notevole popolarità, jQuery AJAX rimane molto diffuso nei progetti legacy o nei team che lo hanno già integrato. L'idea è esattamente la stessa: acquisire ciò che l'utente digita, effettuare una richiesta asincrona e aggiornare il DOM.
Un tipico flusso di lavoro con jQuery in Laravel per una ricerca in tempo reale include solitamente questi passaggi fondamentali: definire una route specifica, creare un controller dedicato, costruire la vista Blade con il campo di input di ricerca e, infine, aggiungere il codice jQuery che attiva la chiamata AJAX quando viene digitato.
Il processo funziona in questo modo: quando l'utente inizia a digitare, jQuery invia una query al server con la stringa di ricerca. Laravel filtra le informazioni nel database, restituisce un oggetto JSON con i risultati corrispondenti e jQuery aggiorna un contenitore HTML sulla pagina per visualizzare le corrispondenze, il tutto in pochi millisecondi.
Il vantaggio di utilizzare jQuery è che semplifica notevolmente la sintassi AJAX ed è molto facile da leggere se la libreria è già presente nel progetto. Tuttavia, introduce una dipendenza aggiuntiva che potrebbe non essere necessaria se si è in grado di lavorare con JavaScript moderno e con le funzionalità fetch native.
Filtraggio e ricerca in tempo reale sul frontend con Alpine.js
Quando i dati da visualizzare sono relativamente pochi (ad esempio, meno di 50 elementi ), non sempre conviene configurare un backend con ricerche complesse. In questi casi, un'opzione molto comoda è quella di filtrare direttamente nel browser utilizzando Alpine.js , senza effettuare richieste al server mentre l'utente digita.
L'idea è di precalcolare una stringa di ricerca per ogni elemento (ad esempio, nome, descrizione e categoria in minuscolo), salvarla in un attributo data-search-text e lasciare che Alpine.js gestisca la visualizzazione o la visualizzazione degli elementi in base al testo inserito in un campo di ricerca.
Il componente Alpine.js può avere una struttura simile a questa: 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;
},
}
Nella vista, ogni scheda o riga di dati avrebbe un attributo data-search-text con il testo già preparato in minuscolo , quindi il filtro si riduce a un includes() in JavaScript, molto veloce per elenchi brevi:
<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>
Inoltre, è possibile visualizzare un blocco di stato vuoto solo quando non ci sono risultati per il termine di ricerca corrente , invitando l'utente a modificare il testo o a cancellare il campo con un pulsante che semplicemente reimposta la ricerca su una stringa vuota.
Questo approccio presenta chiari vantaggi: non ci sono chiamate al server durante la ricerca , l'interazione è praticamente istantanea e la logica rimane altamente locale e facile da sottoporre a debug. È perfetto per selettori rapidi, finestre modali per la selezione di elementi o piccoli cataloghi integrati in una pagina Laravel.
Laravel Scout: ricerca full-text con motori specializzati
Quando la situazione si fa seria e hai bisogno di una ricerca full-text veloce, pertinente e scalabile , la soluzione naturale in Laravel è Laravel Scout. Scout è un livello di integrazione che ti permette di connettere facilmente i tuoi modelli Eloquent con motori di ricerca come Algolia, Meilisearch, il tuo database, collezioni in memoria o persino Elasticsearch tramite controller esterni.
Per iniziare a usare Scout, l'approccio usuale consiste nel creare un nuovo progetto Laravel o riutilizzarne uno esistente , avviarlo con Docker (ad esempio, utilizzando Laravel Sail) e quindi installare la libreria con Composer. Una volta fatto ciò, si pubblica il file di configurazione scout.php e si regolano le variabili d'ambiente in base al driver che si desidera utilizzare.
Un flusso di lavoro tipico prevede l'installazione di Scout tramite Composer, la pubblicazione della sua configurazione e l'abilitazione della coda di indicizzazione con `SCOUT_QUEUE=true` nel file `.env`, in modo che le operazioni che richiedono molte risorse vengano elaborate in background, migliorando i tempi di risposta dell'applicazione. Inoltre, è necessario assicurarsi che `DB_HOST` punti al database in uso, aspetto particolarmente importante se si utilizzano container Docker.
Affinché un modello possa partecipare alle ricerche di Scout, deve essere esplicitamente contrassegnato come ricercabile aggiungendo la caratteristica `Searchable` . Ad esempio, se si dispone di un modello `Train` che rappresenta una tabella di treni con un campo `title`, è possibile definirlo in questo modo:
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';
}
}
Il metodo `searchableAs` consente di personalizzare il nome dell'indice nel motore di ricerca , anziché utilizzare il nome predefinito derivato dal modello. Da lì, Scout gestisce la sincronizzazione delle operazioni di creazione, aggiornamento ed eliminazione con l'indice remoto o locale, a seconda del driver scelto.
Laravel Scout con Algolia: ricerca SaaS velocissima
Algolia è un servizio SaaS focalizzato sulla fornitura di ricerche estremamente rapide e pertinenti su grandi volumi di dati . Offre un pannello web per la gestione di indici, regole di pertinenza, sinonimi e altro ancora, e si integra perfettamente con Laravel tramite Scout e il client PHP ufficiale.
Per utilizzare Algolia con Scout, è necessario installare il client PHP tramite Composer, registrare le proprie credenziali nel file .env (ID applicazione e chiave API di amministrazione) e configurare SCOUT_DRIVER=algolia per indicare a Scout di utilizzare questo motore. È possibile ottenere sia l'ID applicazione che la chiave di amministrazione dalla dashboard di Algolia.
Una volta configurato l'ambiente, è possibile utilizzare metodi come Train::search('text')->paginate(6) direttamente nei controller per eseguire ricerche sui campi indicizzati, ricevendo i risultati in formato Eloquent impaginato, pronti per essere passati a una vista Blade.
Ad esempio , si potrebbe avere un controller di indice che elenca tutti i treni o esegue una ricerca se viene passato un parametro titlesearch, e un metodo create per inserire nuovi treni nell'indice:
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();
}
Nella vista corrispondente, è possibile combinare un modulo per aggiungere nuovi treni e un altro modulo GET con un campo di ricerca nel titolo che attiva la ricerca all'invio. Quindi, è sufficiente scorrere la raccolta di treni e visualizzare i relativi campi in una tabella, sfruttando i link di paginazione generati da Laravel.
Esplora con Meilisearch, database e collezioni
Se preferisci evitare servizi esterni, Meilisearch è un motore di ricerca open source che puoi installare localmente o sulla tua infrastruttura. Scout si integra con Meilisearch in modo molto simile ad Algolia, semplicemente cambiando il driver e aggiungendo le variabili MEILISEARCH_HOST e MEILISEARCH_KEY al file .env.
Per utilizzarlo, installa il client PHP di Meilisearch, imposta SCOUT_DRIVER=meilisearch e punta MEILISEARCH_HOST all'URL dell'istanza (ad esempio, http://127.0.0.1:7700). Se hai già dei record, puoi indicizzarli con il comando php artisan scout:import "App\Models\Train" in modo che il motore li abbia a disposizione.
Per applicazioni di dimensioni ridotte o medie, è possibile scegliere anche il driver di database di Scout , che sfrutta gli indici full-text e le istruzioni LIKE sul database MySQL o PostgreSQL. In questo caso, non è necessario un servizio esterno; è sufficiente impostare SCOUT_DRIVER=database affinché Scout utilizzi il database stesso come motore di ricerca.
Un'altra opzione interessante è il driver di collezione, che funziona con le collezioni Eloquent in memoria . Questo motore filtra i risultati utilizzando `where` e i metodi di filtraggio delle collezioni, ed è compatibile con qualsiasi database supportato da Laravel. È possibile attivarlo con `SCOUT_DRIVER=collection` o modificando il file di configurazione di Scout se si desidera qualcosa di più specifico.
Integrazione con Elasticsearch tramite Explorer
Se le tue esigenze di ricerca implicano la gestione di enormi volumi di dati e l'analisi in tempo reale , Elasticsearch è una soluzione classica. Nell'ecosistema Laravel, un modo moderno per integrarlo con Scout è utilizzare il controller Explorer, che funge da ponte tra i tuoi modelli e un cluster Elasticsearch.
Questo processo in genere prevede l'utilizzo di Docker e di un file docker-compose completo che avvia, oltre ai servizi standard (Laravel, MySQL, Redis, Meilisearch, ecc.), i container di Elasticsearch e Kibana . Successivamente, si installa il pacchetto jeroen-g/explorer tramite Composer e si pubblica il relativo file di configurazione per specificare quali modelli devono essere indicizzati.
Nel file config/explorer.php, puoi registrare i tuoi modelli sotto la chiave indexes, ad esempio aggiungendo App\Models\Train::class . Inoltre, nel file .env, cambia il driver da Scout a Elasticsearch con SCOUT_DRIVER=elastic in modo che tutto punti a Elasticsearch.
All'interno del modello Train, è necessario implementare l'interfaccia Explored e sovrascrivere il metodo mappableAs , che definisce la mappa dei campi da inviare all'indice. Un esempio minimo potrebbe essere:
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,
];
}
}
Da qui, è possibile avviare ricerche su Elasticsearch utilizzando la stessa interfaccia di Scout , beneficiando di tempi di risposta molto bassi e di tutta la potenza di interrogazione di questo motore, ma senza uscire dall'ecosistema Laravel.
Con tutti questi approcci, dal completamento automatico di base con fetch o jQuery, al filtraggio lato frontend con Alpine.js, fino alle ricerche full-text con Laravel Scout e vari driver, Laravel offre una vasta gamma di opzioni per implementare ricerche in tempo reale su misura per le dimensioni del progetto, le prestazioni richieste e l'infrastruttura che si è disposti a gestire.