Three popular front-end libraries for autocomplete search — Select2, jQuery UI's Autocomplete widget, and Typeahead.js — all follow the same underlying pattern with Laravel: a JS widget calls a Laravel endpoint, which returns a filtered JSON array based on the search term.
The shared Laravel endpoint (works for all three libraries)
Route::get('/products/search', function (Request $request) {
return Product::where('name', 'like', '%'.$request->q.'%')
->limit(10)
->get(['id', 'name']);
});
Select2
$('#product-select').select2({
ajax: {
url: '/products/search',
dataType: 'json',
delay: 250,
data: function (params) {
return { q: params.term };
},
processResults: function (data) {
return {
results: data.map(item => ({ id: item.id, text: item.name }))
};
}
}
});
Select2's processResults callback is required to reshape the response into the specific {id, text} format Select2 expects internally — the raw endpoint response and Select2's expected shape rarely match exactly without this transformation step.
jQuery UI Autocomplete
$('#product-input').autocomplete({
source: function (request, response) {
$.get('/products/search', { q: request.term }, function (data) {
response(data.map(item => ({ label: item.name, value: item.name, id: item.id })));
});
},
minLength: 2,
select: function (event, ui) {
$('#product-id').val(ui.item.id);
}
});
jQuery UI expects each result as an object with label (displayed text) and value (what fills the input on selection) — the select callback is where the actual selected item's ID gets captured into a hidden field, since the visible input itself only shows the product name, not its ID.
Typeahead.js (with Bloodhound as its suggestion engine)
const products = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.whitespace,
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: '/products/search?q=%QUERY',
wildcard: '%QUERY'
}
});
$('#product-typeahead').typeahead(null, {
name: 'products',
display: 'name',
source: products
});
Typeahead.js's architecture separates the suggestion engine (Bloodhound, handling caching and the actual remote request) from the input UI itself — the %QUERY placeholder in the URL is replaced with the user's actual typed input at request time.
Debouncing the request across all three libraries
Select2's delay option and a manually implemented debounce for jQuery UI or Typeahead all serve the same purpose — avoiding firing a new request on every single keystroke, instead waiting for a brief pause in typing before actually querying the server, reducing unnecessary load for both the client and server.
Limiting results server-side, not just client-side
The limit(10) in the shared Laravel endpoint above matters regardless of which front-end library is used — without it, a broad search term could return every matching row in the table, which is both wasteful to transmit and unhelpful in a dropdown that can only usefully display a handful of suggestions at once.
Choosing between the three libraries
Select2 is the most full-featured (supporting multi-select, tagging, and rich styling out of the box) and the most commonly reached for in a Laravel/Bootstrap-based admin panel; jQuery UI's Autocomplete is lighter weight, useful when jQuery UI is already a project dependency for other components; Typeahead.js suits a case wanting more control over caching and suggestion ranking via Bloodhound's configuration.