- Laravel cho phép bạn triển khai mọi thứ, từ công cụ tìm kiếm đơn giản với AJAX đến tìm kiếm toàn văn bản nâng cao bằng Laravel Scout và các công cụ tìm kiếm bên ngoài như Algolia, Meilisearch hoặc Elasticsearch.
- Đối với các tìm kiếm nhẹ, việc lọc ở giao diện người dùng bằng Alpine.js hoặc bằng các yêu cầu tìm nạp gốc sẽ tránh làm quá tải máy chủ và cải thiện trải nghiệm của người dùng trong các danh sách nhỏ.
- Laravel Scout tập trung tích hợp với nhiều công cụ tìm kiếm khác nhau và giúp dễ dàng đánh dấu các mô hình có thể tìm kiếm, quản lý chỉ mục và khởi chạy truy vấn thống nhất.
- Việc lựa chọn công cụ (SaaS, mã nguồn mở hoặc cơ sở dữ liệu) phải dựa trên khối lượng dữ liệu, mức độ phức tạp của tìm kiếm và yêu cầu về hiệu suất và bảo trì của dự án.
Khi bắt đầu làm việc với Laravel và cần một công cụ tìm kiếm thời gian thực phản hồi tức thì , bạn rất dễ bị lạc giữa hàng ngàn phương pháp khác nhau: AJAX với fetch, jQuery, Alpine.js, Scout với Algolia hoặc Meilisearch, lọc dữ liệu ở giao diện người dùng, v.v. Tin tốt là hệ sinh thái Laravel đã cung cấp gần như mọi thứ bạn cần để xây dựng một công cụ tìm kiếm mượt mà và nhanh chóng mà không bị choáng ngợp.
Trong bài viết này, bạn sẽ học cách triển khai các loại tìm kiếm thời gian thực khác nhau trong Laravel , từ tính năng tự động hoàn thành AJAX cổ điển đến tìm kiếm toàn văn bằng Laravel Scout và các công cụ tìm kiếm như Algolia, Meilisearch, chính cơ sở dữ liệu hoặc thậm chí là Elasticsearch. Bạn cũng sẽ khám phá các giải pháp thay thế nhẹ nhàng hơn với Alpine.js để lọc dữ liệu trực tiếp trong trình duyệt khi xử lý các tập dữ liệu nhỏ.
Tìm kiếm thời gian thực trong Laravel là gì và hoạt động cơ bản như thế nào?
Ý tưởng đằng sau tìm kiếm thời gian thực là khi người dùng nhập liệu vào một ô văn bản , một truy vấn sẽ được kích hoạt và kết quả được cập nhật mà không cần tải lại trang. Về mặt kỹ thuật, điều này bao gồm ba thành phần chính: hệ thống phụ trợ Laravel, JavaScript của trình duyệt và việc trao đổi dữ liệu ở định dạng JSON.
Một mặt, Laravel đóng vai trò là lớp máy chủ chịu trách nhiệm tiếp nhận yêu cầu, diễn giải các tham số tìm kiếm (văn bản đã nhập), truy vấn cơ sở dữ liệu và trả về phản hồi có cấu trúc, thường ở định dạng JSON. Phản hồi này có thể cho biết thành công, lỗi hoặc không tìm thấy kết quả nào.
Ở phía bên kia, JavaScript xử lý việc lắng nghe các sự kiện của người dùng trên ô tìm kiếm, gửi các yêu cầu bất đồng bộ (AJAX) đến máy chủ và hiển thị dữ liệu trả về trên trang mà không cần làm mới toàn bộ trình duyệt. Điều này có thể được thực hiện bằng cách sử dụng hàm `fetch` gốc, jQuery AJAX hoặc các thư viện phản ứng nhỏ như Alpine.js.
Với cơ chế cơ bản này, bạn có thể xây dựng bất cứ thứ gì, từ một chức năng tự động hoàn thành đơn giản với vài bản ghi , đến một công cụ tìm kiếm toàn văn nâng cao với độ chính xác, phân trang và bộ lọc, dựa trên các thư viện như Laravel Scout và các công cụ tìm kiếm bên ngoài được tối ưu hóa cho tìm kiếm.
Mô hình, tuyến đường và bộ điều khiển cho công cụ tìm kiếm thời gian thực cơ bản
Trước khi bạn bắt đầu tìm hiểu JavaScript, bạn cần đảm bảo phía Laravel được tổ chức tốt: một model Eloquent để tìm kiếm, các tuyến đường rõ ràng và một controller chuyên dụng để xử lý logic tìm kiếm trong thời gian thực.
Bước đầu tiên là tạo một mô hình Eloquent đại diện cho bảng mà bạn sẽ tìm kiếm. Hãy tưởng tượng một bảng quốc gia và một mô hình rất đơn giản có tên là Country , không có dấu thời gian và cho phép gán hàng loạt:
Ví dụ về một mô hình Eloquent tối thiểu dành cho tìm kiếm :
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;
}
Điều này cho thấy mô hình Pais nằm trong không gian tên chuẩn của Laravel , kế thừa từ Model, và cho phép gán bất kỳ trường nào bằng create() bằng cách để trống mảng được bảo vệ. Bằng cách vô hiệu hóa dấu thời gian với public $timestamps = false, bạn sẽ tránh được các sự cố nếu bảng không có các cột created_at và updated_at.
Bước tiếp theo là xác định các tuyến đường sẽ xử lý cả việc hiển thị trên công cụ tìm kiếm và các yêu cầu AJAX . Một phương án rất phổ biến là kết hợp tuyến đường GET để hiển thị giao diện và tuyến đường POST được thiết kế để nhận các truy vấn theo thời gian thực:
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']);
Tuyến đường gốc trả về giao diện chào mừng, trong khi URL /search được dành riêng cho chức năng tìm kiếm . Phương thức index() của bộ điều khiển hiển thị biểu mẫu và ô nhập tìm kiếm, trong khi phương thức search() xử lý các yêu cầu không đồng bộ được gửi từ trình duyệt.
Trong bộ điều khiển, bạn có thể triển khai một mô hình rất thiết thực: chuẩn bị một mảng phản hồi mặc định trong trường hợp xảy ra lỗi và chỉ ghi đè lên nó khi đó thực sự là một yêu cầu AJAX hợp lệ và truy vấn được thực thi mà không gặp sự cố.
Bộ điều khiển có thể có cấu trúc tương tự như sau:
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);
}
}
Đến thời điểm này, bạn đã có chu trình hoàn chỉnh ở phía máy chủ: yêu cầu AJAX đến, xác minh xem đó có thực sự là AJAX hay không, truy vấn bằng cách sử dụng `where like`, và giới hạn số lượng kết quả ở mức hợp lý bằng `take(10)` để tránh làm quá tải cơ sở dữ liệu. Phản hồi luôn được gửi dưới dạng JSON, điều này giúp đơn giản hóa đáng kể công việc ở phía giao diện người dùng.
Chế độ xem Blade và JavaScript để tìm kiếm phản ứng
Sau khi đã chuẩn bị xong mô hình, các tuyến đường và bộ điều khiển, đã đến lúc xây dựng phần hiển thị: một biểu mẫu với trường tìm kiếm và một khối để hiển thị kết quả , cùng với mã JavaScript chịu trách nhiệm thực hiện các yêu cầu ở phía sau.
Giao diện Blade có thể rất đơn giản, dựa vào mã thông báo CSRF mà Laravel chèn vào để xác thực các yêu cầu POST và vào một ô tìm kiếm tiện dụng:
<!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>
Trong ví dụ này, đoạn mã sẽ lắng nghe sự kiện keyup trên ô tìm kiếm , do đó mỗi lần nhấn phím sẽ kích hoạt một yêu cầu tìm nạp dữ liệu đến đường dẫn /search. Văn bản hiện tại của trường được gửi ở định dạng JSON, và các tiêu đề quan trọng như X-Requested-With được bao gồm để chỉ ra rằng đó là AJAX, cùng với mã thông báo CSRF để vượt qua cơ chế bảo vệ gốc của Laravel.
Khi nhận được phản hồi, nó được chuyển đổi thành JSON và một danh sách HTML nhỏ hiển thị kết quả sẽ được tạo động , hoặc một thông báo như "Không tìm thấy kết quả" sẽ được hiển thị nếu truy vấn không trả về dữ liệu. Tất cả điều này diễn ra mà không cần tải lại trang, một cách tự nhiên đối với người dùng.
Mẫu thiết kế này có thể được tinh chỉnh thêm bằng các chi tiết UX nhỏ, chẳng hạn như thêm độ trễ giữa các lần nhấn phím, hiển thị biểu tượng tải hoặc xử lý lỗi mạng để ngăn giao diện bị đơ khi có sự cố xảy ra.
Tìm kiếm trực tiếp với Laravel và AJAX sử dụng jQuery
Mặc dù thư viện `fetch` đã được sử dụng rộng rãi, jQuery AJAX vẫn rất phổ biến trong các dự án cũ hoặc các nhóm đã tích hợp nó. Ý tưởng hoàn toàn giống nhau: thu thập những gì người dùng nhập, thực hiện yêu cầu bất đồng bộ và làm mới DOM.
Quy trình làm việc điển hình với jQuery trong Laravel cho chức năng tìm kiếm trực tiếp thường bao gồm các bước cơ bản sau: định nghĩa một route cụ thể, tạo một controller riêng, xây dựng view Blade với ô nhập tìm kiếm , và cuối cùng thêm mã jQuery để kích hoạt AJAX khi người dùng nhập liệu.
Quá trình hoạt động như sau: khi người dùng bắt đầu gõ, jQuery sẽ gửi một truy vấn đến máy chủ với chuỗi tìm kiếm. Laravel lọc thông tin trong cơ sở dữ liệu, trả về một đối tượng JSON với các kết quả phù hợp, và jQuery cập nhật một vùng chứa HTML trên trang để hiển thị các kết quả phù hợp, tất cả chỉ trong vài mili giây.
Ưu điểm của việc sử dụng jQuery là nó đơn giản hóa đáng kể cú pháp AJAX và rất dễ đọc nếu bạn đã có thư viện này trong dự án của mình. Tuy nhiên, nó lại tạo ra một sự phụ thuộc bổ sung mà có thể không cần thiết nếu bạn có thể làm việc với JavaScript hiện đại và chức năng tìm nạp dữ liệu gốc.
Lọc và tìm kiếm theo thời gian thực trên giao diện người dùng với Alpine.js
Khi dữ liệu cần hiển thị tương đối nhỏ (ví dụ: ít hơn 50 mục ), việc thiết lập một hệ thống phụ trợ với các chức năng tìm kiếm phức tạp không phải lúc nào cũng hiệu quả. Trong những trường hợp này, một lựa chọn rất tiện lợi là lọc trực tiếp trong trình duyệt bằng Alpine.js , mà không cần gửi yêu cầu đến máy chủ trong khi người dùng đang nhập liệu.
Ý tưởng là tính toán trước một chuỗi tìm kiếm cho mỗi mục (ví dụ: tên, mô tả và danh mục viết thường), lưu nó vào thuộc tính data-search-text, và để Alpine.js xử lý việc hiển thị hoặc ẩn các mục dựa trên văn bản được nhập trong trường tìm kiếm.
Thành phần Alpine.js có thể có cấu trúc tương tự như sau: 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;
},
}
Trong giao diện, mỗi thẻ hoặc hàng dữ liệu sẽ có thuộc tính data-search-text với văn bản đã được chuẩn bị sẵn ở dạng chữ thường , do đó bộ lọc được đơn giản hóa thành hàm includes() trong JavaScript, rất nhanh đối với các danh sách ngắn:
<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>
Ngoài ra, bạn có thể hiển thị một khối trạng thái trống chỉ khi không có kết quả nào cho cụm từ tìm kiếm hiện tại , cho phép người dùng chỉnh sửa văn bản hoặc xóa trường bằng một nút đơn giản để đặt lại tìm kiếm về một chuỗi trống.
Cách tiếp cận này có những ưu điểm rõ rệt: không có cuộc gọi đến máy chủ nào trong quá trình tìm kiếm , tương tác gần như tức thời và logic vẫn mang tính cục bộ cao, dễ dàng gỡ lỗi. Nó hoàn hảo cho các bộ chọn nhanh, cửa sổ chọn mục hoặc các danh mục nhỏ được nhúng trong trang Laravel.
Laravel Scout: Tìm kiếm toàn văn bản với các công cụ chuyên dụng
Khi mọi thứ trở nên nghiêm trọng và bạn cần tìm kiếm toàn văn nhanh, chính xác và có khả năng mở rộng , giải pháp tự nhiên trong Laravel chính là Laravel Scout. Scout là một lớp tích hợp cho phép bạn dễ dàng kết nối các mô hình Eloquent của mình với các công cụ tìm kiếm như Algolia, Meilisearch, cơ sở dữ liệu, các bộ sưu tập trong bộ nhớ hoặc thậm chí cả Elasticsearch thông qua các bộ điều khiển bên ngoài.
Để bắt đầu sử dụng Scout, cách thông thường là tạo một dự án Laravel mới hoặc sử dụng lại một dự án hiện có , khởi chạy nó bằng Docker (ví dụ: sử dụng Laravel Sail), và sau đó cài đặt thư viện bằng Composer. Sau khi hoàn tất, bạn xuất bản tệp cấu hình scout.php và điều chỉnh các biến môi trường theo trình điều khiển bạn muốn sử dụng.
Quy trình làm việc điển hình là cài đặt Scout bằng Composer, xuất bản cấu hình của nó và bật hàng đợi lập chỉ mục bằng cách thêm `SCOUT_QUEUE=true` vào tệp `.env` để các thao tác tốn nhiều tài nguyên được xử lý trong nền, cải thiện thời gian phản hồi của ứng dụng. Ngoài ra, bạn cần đảm bảo rằng `DB_HOST` trỏ đến cơ sở dữ liệu bạn đang sử dụng, điều này đặc biệt quan trọng nếu bạn đang sử dụng container Docker.
Để một mô hình tham gia vào các tìm kiếm của Scout, nó phải được đánh dấu rõ ràng là có thể tìm kiếm được bằng cách thêm thuộc tính `Searchable` . Ví dụ, nếu bạn có một mô hình `Train` đại diện cho một bảng các chuyến tàu với trường `title`, bạn có thể định nghĩa nó như sau:
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';
}
}
Phương thức `searchableAs` cho phép bạn tùy chỉnh tên chỉ mục trong công cụ tìm kiếm , thay vì sử dụng tên mặc định được tạo ra từ mô hình. Từ đó, Scout sẽ xử lý việc đồng bộ hóa các thao tác tạo, cập nhật và xóa với chỉ mục từ xa hoặc cục bộ, tùy thuộc vào trình điều khiển được chọn.
Laravel Scout với Algolia: Tìm kiếm SaaS nhanh như chớp
Algolia là một dịch vụ SaaS tập trung vào việc cung cấp khả năng tìm kiếm nhanh chóng và chính xác trên khối lượng dữ liệu lớn . Nó có một bảng điều khiển web để quản lý chỉ mục, quy tắc độ liên quan, từ đồng nghĩa, v.v., và tích hợp liền mạch với Laravel thông qua Scout và máy khách PHP chính thức.
Để sử dụng Algolia với Scout, bạn cần cài đặt client PHP của nó bằng Composer, đăng ký thông tin xác thực của mình trong file .env (ID ứng dụng và Khóa API quản trị), và cấu hình SCOUT_DRIVER=algolia để cho Scout biết sử dụng engine này. Bạn có thể lấy cả ID ứng dụng và khóa quản trị từ bảng điều khiển Algolia.
Sau khi thiết lập môi trường, bạn có thể sử dụng các phương thức như Train::search('text')->paginate(6) trực tiếp trong bộ điều khiển của mình để thực hiện tìm kiếm trên các trường được lập chỉ mục, nhận kết quả ở định dạng Eloquent được phân trang sẵn sàng để chuyển đến chế độ xem Blade.
Ví dụ , bạn có thể có một bộ điều khiển chỉ mục liệt kê tất cả các chuyến tàu hoặc thực hiện tìm kiếm nếu tham số titlesearch được truyền vào, và một phương thức tạo để chèn các chuyến tàu mới vào chỉ mục:
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();
}
Trong giao diện tương ứng, bạn có thể kết hợp một biểu mẫu để thêm các chuyến tàu mới và một biểu mẫu GET khác với trường tìm kiếm tiêu đề, kích hoạt tìm kiếm khi gửi biểu mẫu. Sau đó, bạn chỉ cần lặp qua tập hợp các chuyến tàu và hiển thị các trường của chúng trong một bảng, tận dụng các liên kết phân trang do Laravel tạo ra.
Trinh sát với Meilisearch, cơ sở dữ liệu và bộ sưu tập
Nếu bạn muốn tránh sử dụng các dịch vụ bên ngoài, Meilisearch là một công cụ tìm kiếm mã nguồn mở mà bạn có thể triển khai cục bộ hoặc trên cơ sở hạ tầng của mình. Scout tích hợp với Meilisearch theo cách rất giống với Algolia, chỉ cần thay đổi trình điều khiển và thêm các biến MEILISEARCH_HOST và MEILISEARCH_KEY vào tệp .env.
Để sử dụng, hãy cài đặt ứng dụng khách PHP Meilisearch, thiết lập SCOUT_DRIVER=meilisearch và trỏ MEILISEARCH_HOST đến URL của phiên bản (ví dụ: http://127.0.0.1:7700). Nếu bạn đã có các bản ghi, bạn có thể lập chỉ mục chúng bằng lệnh php artisan scout:import "App\Models\Train" để công cụ có thể truy cập chúng.
Đối với các ứng dụng có quy mô nhỏ hoặc trung bình, bạn cũng có thể chọn trình điều khiển cơ sở dữ liệu của Scout , tận dụng các chỉ mục tìm kiếm toàn văn và câu lệnh LIKE trên cơ sở dữ liệu MySQL hoặc PostgreSQL của bạn. Trong trường hợp này, bạn không cần dịch vụ bên ngoài; chỉ cần đặt SCOUT_DRIVER=database để Scout sử dụng chính cơ sở dữ liệu làm công cụ tìm kiếm của nó.
Một tùy chọn thú vị khác là trình điều khiển tập hợp (collection driver), hoạt động với các tập hợp Eloquent trong bộ nhớ . Công cụ này lọc kết quả bằng cách sử dụng mệnh đề `where` và các phương thức lọc tập hợp, và nó tương thích với bất kỳ cơ sở dữ liệu nào được Laravel hỗ trợ. Bạn có thể kích hoạt nó bằng `SCOUT_DRIVER=collection` hoặc bằng cách điều chỉnh tệp cấu hình Scout nếu bạn muốn điều chỉnh cụ thể hơn.
Tích hợp với Elasticsearch bằng Explorer
Nếu nhu cầu tìm kiếm của bạn liên quan đến việc xử lý khối lượng dữ liệu khổng lồ và phân tích thời gian thực , Elasticsearch là một lựa chọn kinh điển. Trong hệ sinh thái Laravel, một cách hiện đại để tích hợp nó với Scout là sử dụng bộ điều khiển Explorer, hoạt động như một cầu nối giữa các mô hình của bạn và cụm Elasticsearch.
Quá trình này thường bao gồm làm việc với Docker và một tệp docker-compose đầy đủ, ngoài các dịch vụ thông thường (Laravel, MySQL, Redis, Meilisearch, v.v.), còn khởi chạy các container Elasticsearch và Kibana . Sau đó, bạn cài đặt gói jeroen-g/explorer thông qua Composer và xuất bản tệp cấu hình của nó để chỉ định các mô hình nào cần được lập chỉ mục.
Trong tệp config/explorer.php, bạn có thể đăng ký các mô hình của mình dưới khóa indexes, ví dụ bằng cách thêm App\Models\Train::class . Ngoài ra, bạn thay đổi trình điều khiển từ Scout sang Elasticsearch trong tệp .env bằng cách thêm SCOUT_DRIVER=elastic để mọi thứ trỏ đến Elasticsearch.
Trong mô hình Train, bạn cần triển khai giao diện Explored và ghi đè phương thức mappableAs , phương thức này định nghĩa bản đồ các trường sẽ được gửi đến chỉ mục. Một ví dụ tối thiểu như sau:
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,
];
}
}
Từ đây, bạn có thể thực hiện tìm kiếm trên Elasticsearch bằng giao diện Scout tương tự , tận dụng được thời gian phản hồi rất chậm và toàn bộ sức mạnh truy vấn của công cụ này, mà không cần rời khỏi hệ sinh thái Laravel.
Với tất cả các phương pháp tiếp cận này—từ tính năng tự động hoàn thành cơ bản với fetch hoặc jQuery, đến tính năng lọc giao diện người dùng với Alpine.js, đến tính năng tìm kiếm toàn văn với Laravel Scout và nhiều trình điều khiển khác nhau— Laravel cung cấp cho bạn một loạt các tùy chọn khổng lồ để triển khai tìm kiếm thời gian thực phù hợp với quy mô dự án, hiệu suất bạn cần và cơ sở hạ tầng bạn sẵn sàng duy trì.