Memilih Mekanisme Penyimpanan di Peramban
Peramban modern menyediakan beberapa tempat penyimpanan data dengan karakteristik, masa hidup, dan tingkat keamanan yang berbeda. Perhatikan perbandingan berikut sebelum memutuskan tempat menyimpan data:
| Mekanisme | Kapasitas Umum | Masa Hidup Data | Apakah Otomatis Dikirim ke Server di Setiap Request HTTP? | Kasus Penggunaan Terbaik |
|---|---|---|---|---|
localStorage | ~5 MB per origin | Permanen hingga dihapus oleh skrip atau pengguna. | Tidak (murni di sisi klien). | Preferensi tema (gelap/terang), progres modul belajar, penanda (bookmark), draf formulir. |
sessionStorage | ~5 MB per origin | Hanya selama satu tab terbuka (hilang saat tab ditutup). | Tidak (murni di sisi klien). | State langkah formulir multi-tahap (wizard) atau posisi filter sementara di tab tersebut. |
IndexedDB | Ratusan MB hingga GB (sesuai ruang diska) | Permanen hingga dihapus. | Tidak (asinkron, mendukung transaksi & indeks). | Aplikasi offline-first, menyimpan ribuan objek terstruktur, atau berkas biner (Blob/File). |
HTTP Cookie (HttpOnly) | ~4 KB | Sesuai Max-Age / Expires. | Ya, selalu dikirim otomatis melalui header Cookie. | Identitas sesi autentikasi (session_id) yang wajib dilindungi dari JavaScript! |
localStorage!Semua data di dalam localStorage dan sessionStorage dapat dibaca secara bebas oleh setiap baris JavaScript yang berjalan di halaman tersebut. Jika situs Anda mengalami satu saja celah XSS (atau memuat skrip pihak ketiga yang disusupi), penyerang cukup memanggil localStorage.getItem("token") untuk mencuri akun pengguna! Untuk sesi login produksi, gunakan Cookie dengan atribut HttpOnly, Secure, dan SameSite yang diatur oleh server.
1. Menggunakan localStorage dan sessionStorage dengan Aman
Kunci dan nilai yang disimpan di dalam localStorage maupun sessionStorage selalu bertipe string. Jika Anda langsung menyimpan objek localStorage.setItem("profil", { nama: "Adi" }), nilainya akan rusak menjadi string "[object Object]"!
Oleh karena itu, selalu gunakan JSON.stringify() saat menyimpan objek/array, dan JSON.parse() saat membacanya kembali—serta selalu bungkus di dalam try...catch!
Mengapa localStorage Wajib Dibungkus try...catch?
setItem()dapat melemparQuotaExceededError: Ketika penyimpanan penuh atau pada mode penyamaran (private browsing) tertentu yang membatasi kuota.- Pengguna dapat memblokir data situs: Memanggil
localStoragesaat pengaturan privasi peramban memblokir penyimpanan lokal dapat melemparSecurityError. JSON.parse()dapat melemparSyntaxError: Jika data di penyimpanan lokal termodifikasi atau rusak.
// Pembungkus penyimpanan lokal yang aman dan siap pakai di produksi
export const penyimpananLokal = {
ambil(kunci, nilaiBawaan = null) {
try {
const teksMentah = window.localStorage.getItem(kunci);
if (teksMentah === null) return nilaiBawaan;
return JSON.parse(teksMentah);
} catch (galat) {
console.warn(`Gagal membaca kunci '${kunci}' dari localStorage:`, galat);
return nilaiBawaan;
}
},
simpan(kunci, nilai) {
try {
const teksJson = JSON.stringify(nilai);
window.localStorage.setItem(kunci, teksJson);
return true;
} catch (galat) {
console.warn(`Gagal menyimpan kunci '${kunci}' ke localStorage:`, galat);
return false;
}
},
hapus(kunci) {
try {
window.localStorage.removeItem(kunci);
} catch (galat) {
console.warn(`Gagal menghapus kunci '${kunci}':`, galat);
}
}
};
(Catatan: Pola inilah yang dipakai oleh Kodeloka sendiri untuk menyimpan progres modul, penanda, dan catatan belajar Anda di peramban!)
2. Menyinkronkan State Antar-Tab secara Real-Time dengan Event storage
Bagaimana jika pengguna membuka situs Kodeloka di Tab A dan Tab B secara bersamaan, lalu di Tab A ia mengganti tema menjadi Mode Gelap atau menyelesaikan Modul 5?
Ketika localStorage diubah di Tab A, peramban secara otomatis menembakkan event "storage" pada objek window di **semua tab lain (Tab B, Tab C) yang membuka domain (origin) yang sama**:
window.addEventListener("storage", (event) => {
// event.key -> Nama kunci yang berubah (mis. "kodeloka:tema")
// event.oldValue -> Nilai string lama sebelum berubah
// event.newValue -> Nilai string baru setelah berubah
if (event.key === "kodeloka:tema" && event.newValue) {
const temaBaru = JSON.parse(event.newValue);
document.documentElement.setAttribute("data-tema", temaBaru);
}
});
3. Pengantar IndexedDB untuk Data Berukuran Besar dan Asinkron
Karena localStorage bersifat sinkron (memblokir utas utama saat membaca/menulis string besar) dan dibatasi sekitar 5 MB, jangan gunakan localStorage untuk menyimpan ribuan baris katalog atau dokumen panjang.
Untuk kebutuhan tersebut, gunakan IndexedDB—basis data NoSQL transaksional bawaan peramban yang bekerja secara asinkron:
// Membuka koneksi IndexedDB dan membungkusnya dengan Promise modern
function bukaDatabaseCatatan() {
return new Promise((resolve, reject) => {
const permintaan = window.indexedDB.open("KodelokaDB", 1);
// Dijalankan saat basis data pertama kali dibuat atau versinya dinaikkan
permintaan.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains("catatan_modul")) {
db.createObjectStore("catatan_modul", { keyPath: "idModul" });
}
};
permintaan.onsuccess = () => resolve(permintaan.result);
permintaan.onerror = () => reject(permintaan.error);
});
}
Latihan Praktik: Menyimpan Draf Catatan Otomatis (Auto-Save) dan Sinkronisasi Antar-Tab
Buatlah berkas penyimpanan-catatan.html dan penyimpanan.js:
- Sediakan sebuah
<textarea id="editor-catatan">dan indikator status<output id="status-simpan">. - Setiap kali pengguna mengetik (event
"input"), simpan isi catatan beserta waktu pembaruannya kelocalStoragemenggunakan pembungkustry...catchyang aman. - Saat halaman dimuat ulang, pulihkan kembali isi
<textarea>darilocalStorage. - Tambahkan pendengar
window.addEventListener("storage", ...)agar ketika halaman dibuka di dua tab berdampingan, ketikan di Tab 1 langsung muncul di Tab 2!