Pengenalan IndexedDB

Basis data sisi klien untuk menyimpan data dalam jumlah besar secara offline.

Daftar isi

Ketika data yang harus disimpan melampaui sekadar preferensi, misalnya ratusan catatan lengkap dengan tanggalnya, Web Storage mulai terasa sempit dan IndexedDB menjadi kelanjutannya. IndexedDB adalah basis data yang hidup di dalam browser, menyimpan object dalam jumlah besar, dapat dicari lewat index, dan bekerja secara asinkron sehingga halaman tidak pernah tertahan. Topik ini memperkenalkan susunan bagiannya beserta pola pembukaan database yang paling sering dipakai.

Kapan localStorage tidak cukup#

Web Storage menyimpan string dengan kapasitas kecil, umumnya sekitar lima megabita per origin, dan setiap operasinya berjalan sinkron dengan kode halaman. Batas itu terasa ketika isinya berupa ratusan object yang saling terkait, misalnya arsip catatan harian yang perlu dicari menurut tanggal. Menyimpan semuanya sebagai string JSON besar memaksa pembongkaran penuh setiap kali satu item saja dibutuhkan.

IndexedDB menjawab kekurangan tersebut dengan tiga sifatnya. Kapasitasnya jauh lebih besar dan tumbuh mengikuti ruang penyimpanan perangkat. Isinya berupa object JavaScript utuh yang tidak perlu diurai manual. Setiap operasinya berjalan asinkron lewat event sehingga halaman tetap responsif. Aplikasi catatan luring, kumpulan artikel tersimpan, dan hasil kerja yang harus tetap ada tanpa jaringan adalah contoh pemakaiannya.

Database, object store, dan transaksi#

Susunan IndexedDB mengikuti istilah basis data pada umumnya. Satu database dipisahkan per origin dan dikenali lewat namanya. Di dalamnya terdapat satu atau lebih object store, yaitu wadah object sejenis yang mirip tabel dengan satu kunci untuk tiap isinya. Nama storenya bebas ditentukan sendiri, dan satu database yang sama boleh memuat banyak store untuk jenis data yang berbeda, misalnya catatan dan lampiran. Pencarian menurut kunci sudah cepat, sedangkan pencarian menurut bidang lain dipercepat oleh index, daftar bantu yang dibuat di atas satu bidang object store.

Semua pembacaan dan penulisan wajib berada di dalam transaksi, yaitu paket pekerjaan yang dijanjikan selesai utuh atau tidak sama sekali. Transaksi dibuat dengan menyebut object store yang akan disentuh beserta modenya, readonly untuk membaca dan readwrite untuk mengubah. Transaksi yang sudah selesai tidak dapat dipakai ulang, setiap kelompok operasi baru memerlukan transaksi baru, dan aturan ini justru menjaga data tetap konsisten. Di dalamnya, operasi menjadi permintaan (request) yang memancarkan event success atau error, dan pola event inilah yang membedakan IndexedDB dari API sinkron milik Web Storage.

Diagram berikut merangkum alurnya, dari pembukaan database sampai operasi pada object store yang berada di dalam transaksi.

onupgradeneededhanya saat versi dinaikkanindexedDB.openonsuccesstransactionput() / get()dijalankan di dalamtransaksiSetiap permintaan memancarkan event success atau error.
Alur IndexedDB, database dibuka, lalu setiap put dan get dijalankan di dalam transaksi yang memancarkan event

Membuka database dan menulis data#

Pintu masuknya adalah indexedDB.open() yang menerima nama database dan nomor versinya. Panggilan ini memancarkan onupgradeneeded hanya ketika database belum ada atau versinya dinaikkan, dan di situlah object store dibuat dengan createObjectStore(). Setelah persiapan selesai, event onsuccess membawa database yang siap dipakai, sedangkan onerror menampung kegagalan pembukaannya.

js Contoh: pola membuka database dan menyiapkan object store
// tidak-dijalankan
const permintaan = indexedDB.open("arsip-catatan", 1);

permintaan.onupgradeneeded = () => {
  permintaan.result.createObjectStore("catatan");
};

permintaan.onsuccess = () => {
  const db = permintaan.result; // database siap dipakai
};

permintaan.onerror = () => {
  console.log("Database gagal dibuka");
};

Menulis dan membaca mengikuti pola transaksi yang sudah dibahas. Satu transaksi dibuat untuk object store catatan dengan mode readwrite, permintaan put() menerima object beserta kuncinya, dan event oncomplete milik transaksi menandakan seluruh pekerjaannya tuntas. Kuncinya boleh diberikan manual seperti di bawah ini, atau dipercayakan ke store lewat opsi autoIncrement.

js Contoh: menyimpan satu catatan di dalam transaksi
// tidak-dijalankan
const transaksi = db.transaction("catatan", "readwrite");
const store = transaksi.objectStore("catatan");

store.put({ teks: "Rapat kelas pukul 10", tanggal: "2026-11-02" }, 1);

transaksi.oncomplete = () => {
  console.log("Catatan tersimpan");
};

Demo berikut memperagakan pola lengkapnya pada satu object store sederhana. Tulis satu catatan, tekan Simpan catatan, dan isinya akan dimuat kembali dari IndexedDB setiap demo dibuka, termasuk setelah halaman dimuat ulang. Bila lingkungan yang dipakai memblokir IndexedDB, panel keterangan menampilkan pesan penggantinya dan halaman tetap berjalan.

Kode sumber demo
<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="UTF-8">
  <title>Demo menyimpan satu catatan di IndexedDB</title>
  <style>
    body { font-family: "Segoe UI", Arial, sans-serif; margin: 14px; font-size: 14px; color: #1f2937; }
    label { display: block; font-weight: 600; margin-bottom: 6px; }
    input { padding: 6px 8px; width: 260px; max-width: 100%; }
    button { padding: 6px 12px; margin: 10px 0; cursor: pointer; display: block; }
    #isi { padding: 10px; background: #f1f5f9; border: 1px solid #94a3b8; border-radius: 8px; max-width: 360px; min-height: 20px; }
    #info { margin-top: 10px; font-size: 13px; color: #64748b; max-width: 380px; }
  </style>
</head>
<body>
  <label for="teks">Catatan terakhir</label>
  <input id="teks" type="text" placeholder="Tulis satu catatan" autocomplete="off">
  <button id="simpan" type="button">Simpan catatan</button>
  <div id="isi"></div>
  <div id="info"></div>
  <script src="index.js"></script>

<script>
// Demo IndexedDB: menyimpan satu catatan ke object store dan menampilkannya.

const info = document.querySelector("#info");
const isi = document.querySelector("#isi");
const teks = document.querySelector("#teks");
let db = null;

try {
  const permintaan = indexedDB.open("demo-catatan", 1);

  permintaan.onupgradeneeded = () => {
    permintaan.result.createObjectStore("catatan");
  };

  permintaan.onsuccess = () => {
    db = permintaan.result;
    info.textContent = "Database siap dipakai.";
    tampilkan();
  };

  permintaan.onerror = () => {
    info.textContent = "Database gagal dibuka di lingkungan ini.";
  };
} catch (error) {
  info.textContent = "IndexedDB tidak tersedia di lingkungan ini.";
}

function simpan() {
  if (!db) return;
  const transaksi = db.transaction("catatan", "readwrite");
  transaksi.objectStore("catatan").put(teks.value, "terakhir");
  transaksi.oncomplete = tampilkan;
  transaksi.onerror = () => {
    info.textContent = "Penyimpanan gagal dijalankan.";
  };
}

function tampilkan() {
  if (!db) return;
  const transaksi = db.transaction("catatan", "readonly");
  const permintaan = transaksi.objectStore("catatan").get("terakhir");
  permintaan.onsuccess = () => {
    isi.textContent = permintaan.result === undefined
      ? "(belum ada catatan)"
      : permintaan.result;
  };
}

document.querySelector("#simpan").addEventListener("click", simpan);

</script>
<script>
// Demo IndexedDB: menyimpan satu catatan ke object store dan menampilkannya.

const info = document.querySelector("#info");
const isi = document.querySelector("#isi");
const teks = document.querySelector("#teks");
let db = null;

try {
  const permintaan = indexedDB.open("demo-catatan", 1);

  permintaan.onupgradeneeded = () => {
    permintaan.result.createObjectStore("catatan");
  };

  permintaan.onsuccess = () => {
    db = permintaan.result;
    info.textContent = "Database siap dipakai.";
    tampilkan();
  };

  permintaan.onerror = () => {
    info.textContent = "Database gagal dibuka di lingkungan ini.";
  };
} catch (error) {
  info.textContent = "IndexedDB tidak tersedia di lingkungan ini.";
}

function simpan() {
  if (!db) return;
  const transaksi = db.transaction("catatan", "readwrite");
  transaksi.objectStore("catatan").put(teks.value, "terakhir");
  transaksi.oncomplete = tampilkan;
  transaksi.onerror = () => {
    info.textContent = "Penyimpanan gagal dijalankan.";
  };
}

function tampilkan() {
  if (!db) return;
  const transaksi = db.transaction("catatan", "readonly");
  const permintaan = transaksi.objectStore("catatan").get("terakhir");
  permintaan.onsuccess = () => {
    isi.textContent = permintaan.result === undefined
      ? "(belum ada catatan)"
      : permintaan.result;
  };
}

document.querySelector("#simpan").addEventListener("click", simpan);

</script></body>
</html>

Kesalahan umum#

  1. Menulis operasi sebelum onsuccess. Database belum tersedia selama panggilan open masih berjalan, sehingga variabel db masih null dan pembentukan transaksinya melempar error. Seluruh pekerjaan pertama harus menunggu di dalam handler onsuccess.
  2. Lupa membuat object store pada versi yang baru. createObjectStore() hanya sah dipanggil di dalam onupgradeneeded, dan store yang terlewat membuat transaksi berikutnya gagal karena nama storenya tidak dikenal. Naikkan nomor versi dan lengkapi penyiapannya di sana.
  3. Menyela transaksi dengan jeda asinkron. Transaksi IndexedDB ditutup otomatis ketika tidak ada permintaan yang mengantre, sehingga jeda antar operasi membuat operasi berikutnya ditolak. Antrekan seluruh permintaan dalam satu giliran eksekusi yang sama.

Ringkasan#

  • IndexedDB adalah basis data di dalam browser untuk object dalam jumlah besar, dengan kapasitas jauh melampaui Web Storage dan operasi asinkron berbasis event.
  • Susunannya bertingkat, database per origin menampung object store berkunci, dan index mempercepat pencarian menurut bidang selain kunci.
  • Setiap put dan get berada di dalam transaksi bermode readonly atau readwrite, lalu memancarkan event success atau error.
  • indexedDB.open() memicu onupgradeneeded untuk penyiapan object store, dan hasilnya dipakai di handler onsuccess.

Referensi#