Cara Membuat Live Search dengan JavaScript dan PHP

Kamu punya tabel berisi ratusan baris data dan pengguna harus mengetik kata kunci lalu menekan tombol cari setiap kali ingin menyaringnya. Live search memindahkan pekerjaan itu ke belakang layar: hasil muncul saat pengguna masih mengetik. Implementasinya butuh dua bagian yang saling terhubung, input di sisi JavaScript dan endpoint pencarian di sisi PHP.

Alur kerja live search dengan JavaScript dan PHP

Ada tiga komponen yang harus jalan berurutan. Pertama, event listener menangkap ketikan pada input. Kedua, JavaScript mengirim nilai input ke server tanpa memuat ulang halaman. Ketiga, PHP menerima kata kunci, menjalankan query, dan mengembalikan hasil dalam format yang mudah dibaca JavaScript, biasanya JSON.

Pisahkan endpoint pencarian ke file tersendiri. Kalau logika pencarian ditumpuk di halaman yang sama dengan tampilan, kamu akan kesulitan menangani dua jenis permintaan sekaligus dan rawan mencampur HTML dengan JSON.

Menyiapkan endpoint pencarian di PHP

Buat file search.php yang hanya bertugas menerima kata kunci dan mengembalikan data. Gunakan PDO dengan prepared statement supaya kata kunci dari pengguna tidak bisa merusak query. Kalau kamu belum terbiasa dengan pola ini, baca dulu cara mencegah SQL injection di PHP dengan prepared statement.

<?php
header('Content-Type: application/json');

$keyword = trim($_GET['q'] ?? '');
if ($keyword === '') {
    echo json_encode([]);
    exit;
}

$pdo = new PDO('mysql:host=localhost;dbname=latihan;charset=utf8mb4', 'root', '');
$stmt = $pdo->prepare('SELECT id, nama FROM produk WHERE nama LIKE ? ORDER BY nama LIMIT 10');
$stmt->execute(['%' . $keyword . '%']);

echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));

Tanda % di kiri dan kanan kata kunci membuat pencarian bersifat mengandung, bukan hanya cocok persis. Batasi hasil dengan LIMIT karena pengguna biasanya hanya butuh beberapa saran pertama, bukan seluruh isi tabel.

Menangani input dengan debounce di JavaScript

Setiap ketikan memicu satu permintaan ke server. Kalau pengguna mengetik sepuluh huruf dengan cepat, sepuluh permintaan berangkat hampir bersamaan dan hasil yang tampil bisa kacau karena respons datang tidak berurutan. Debounce menunda pengiriman sampai pengguna berhenti mengetik selama jeda tertentu.

const input = document.querySelector('#cari');
const hasil = document.querySelector('#hasil');
let timer;

input.addEventListener('input', () => {
  clearTimeout(timer);
  timer = setTimeout(async () => {
    const q = input.value.trim();
    if (q.length < 2) {
      hasil.innerHTML = '';
      return;
    }
    const res = await fetch('search.php?q=' + encodeURIComponent(q));
    const data = await res.json();
    hasil.innerHTML = data
      .map(item => '<li>' + item.nama + '</li>')
      .join('');
  }, 300);
});

Jeda 300 milidetik cukup untuk menahan permintaan yang tidak perlu tanpa membuat pengguna merasa lambat. Batas minimal dua karakter mencegah query dijalankan untuk satu huruf yang hasilnya terlalu luas.

Menyusun tampilan hasil tanpa merusak HTML

Contoh di atas menyisipkan item.nama langsung ke dalam innerHTML. Cara ini berbahaya kalau data berisi karakter seperti < atau > karena browser akan menganggapnya tag. Bungkus data dengan fungsi yang mengubah karakter khusus menjadi entitas, atau pakai textContent pada elemen yang dibuat lewat createElement.

  • Gunakan textContent saat mengisi teks dari database.
  • Kalau tetap memakai innerHTML, escape dulu karakter &, <, >, ", dan '.
  • Jangan pernah menyisipkan nilai mentah dari server ke atribut HTML tanpa escape.

Persoalan escaping ini sering muncul saat data PHP dikirim ke JavaScript. Ulasan lengkapnya ada di mengapa array PHP kadang tidak muncul di JavaScript, yang membahas jebakan saat menyalin data antar dua bahasa tersebut.

Membatalkan permintaan yang sudah usang

Debounce mengurangi jumlah permintaan, tetapi tidak menjamin urutan respons. Pengguna bisa mengetik “bo”, lalu menghapusnya menjadi “b”, dan respons untuk “bo” tiba lebih dulu sehingga hasil lama menimpa hasil baru. Solusinya, simpan controller terakhir dan batalkan sebelum mengirim permintaan berikutnya.

let controller;

async function cariData(q) {
  if (controller) controller.abort();
  controller = new AbortController();
  const res = await fetch('search.php?q=' + encodeURIComponent(q), {
    signal: controller.signal
  });
  return res.json();
}

Bungkus pemanggilan dalam try/catch karena fetch yang dibatalkan akan melempar error. Abaikan error dengan nama AbortError supaya tidak muncul di konsol saat pengguna mengetik cepat.

Menangani kondisi tanpa hasil dan saat input kosong

Hasil kosong tetap perlu umpan balik. Tampilkan teks singkat seperti “Tidak ada produk yang cocok” di dalam wadah hasil, lalu hapus teks itu ketika pengguna mengetik lagi. Kalau input kosong atau kurang dari dua karakter, kosongkan wadah dan hentikan permintaan.

Untuk data yang jumlahnya besar, pertimbangkan menambahkan indeks pada kolom yang sering dicari di database. Query dengan LIKE yang diawali tanda % tidak bisa memanfaatkan indeks biasa, jadi kalau tabel sudah puluhan ribu baris, pencarian penuh akan terasa lambat. Alternatifnya, gunakan full-text search bawaan database atau batasi pencarian pada kolom yang lebih sempit.

Struktur dasar ini juga dipakai di panduan kolom pencarian PHP dan filter data, bedanya di sana hasil dimuat ulang lewat form biasa. Kamu bisa memakai keduanya berdampingan: live search untuk saran cepat, tombol cari untuk hasil lengkap dengan filter tambahan.

Ringkasnya, live search yang rapi memerlukan endpoint PHP yang mengembalikan JSON dan memakai prepared statement, debounce di JavaScript untuk menahan permintaan berlebihan, dan pengelolaan urutan respons agar hasil lama tidak menimpa hasil baru. Tambahkan escaping pada data yang ditampilkan, lalu uji dengan koneksi lambat untuk melihat perilakunya saat respons datang tidak berurutan.

Pertanyaan yang Sering Diajukan

Apakah live search membebani server lebih besar daripada pencarian biasa?

Jumlah permintaannya lebih banyak karena setiap jeda ketikan memicu satu request. Beban itu bisa ditekan dengan debounce, batas minimal karakter, dan LIMIT pada query. Kalau trafik tinggi, tambahkan cache untuk kata kunci yang sering dicari.

Kenapa hasil pencarian muncul dua kali atau berkedip?

Biasanya karena dua respons datang berurutan dan keduanya menulis ke elemen hasil yang sama. Batalkan permintaan sebelumnya memakai AbortController, atau bandingkan nilai input saat respons tiba dengan nilai saat permintaan dikirim.

Bisakah live search dipakai tanpa fetch API?

Bisa memakai XMLHttpRequest, tetapi fetch lebih ringkas untuk kebutuhan ini. Kalau proyekmu memakai jQuery, fungsi $.ajax juga bekerja dengan pola yang sama. Yang penting tetap sama: batasi frekuensi permintaan dan tangani urutan respons.