JavaScript dan IndexedDB

JavaScript dan IndexedDB

Dalam artikel ini, kami akan menjelaskan JavaScript dan IndexedDB.

Tutorial ini memberikan penjelasan langkah-demi-langkah tentang JavaScript dan IndexedDB, termasuk kode contoh praktis di setiap langkah untuk membantu memperdalam pemahaman Anda.

YouTube Video

javascript-indexed-db.html
  1<!DOCTYPE html>
  2<html lang="en">
  3<head>
  4  <meta charset="UTF-8">
  5  <title>JavaScript &amp; HTML</title>
  6  <style>
  7    * {
  8        box-sizing: border-box;
  9    }
 10
 11    body {
 12        margin: 0;
 13        padding: 1em;
 14        padding-bottom: 10em;
 15        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 16        background-color: #f7f9fc;
 17        color: #333;
 18        line-height: 1.6;
 19    }
 20
 21    .container {
 22        max-width: 800px;
 23        margin: 0 auto;
 24        padding: 1em;
 25        background-color: #ffffff;
 26        border: 1px solid #ccc;
 27        border-radius: 10px;
 28        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 29    }
 30
 31    .container-flex {
 32        display: flex;
 33        flex-wrap: wrap;
 34        gap: 2em;
 35        max-width: 1000px;
 36        margin: 0 auto;
 37        padding: 1em;
 38        background-color: #ffffff;
 39        border: 1px solid #ccc;
 40        border-radius: 10px;
 41        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 42    }
 43
 44    .left-column, .right-column {
 45        flex: 1 1 200px;
 46        min-width: 200px;
 47    }
 48
 49    h1, h2 {
 50        font-size: 1.2rem;
 51        color: #007bff;
 52        margin-top: 0.5em;
 53        margin-bottom: 0.5em;
 54        border-left: 5px solid #007bff;
 55        padding-left: 0.6em;
 56        background-color: #e9f2ff;
 57    }
 58
 59    button {
 60        display: block;
 61        margin: 1em auto;
 62        padding: 0.75em 1.5em;
 63        font-size: 1rem;
 64        background-color: #007bff;
 65        color: white;
 66        border: none;
 67        border-radius: 6px;
 68        cursor: pointer;
 69        transition: background-color 0.3s ease;
 70    }
 71
 72    button:hover {
 73        background-color: #0056b3;
 74    }
 75
 76    #output {
 77        margin-top: 1em;
 78        background-color: #1e1e1e;
 79        color: #0f0;
 80        padding: 1em;
 81        border-radius: 8px;
 82        min-height: 200px;
 83        font-family: Consolas, monospace;
 84        font-size: 0.95rem;
 85        overflow-y: auto;
 86        white-space: pre-wrap;
 87    }
 88
 89    .highlight {
 90        outline: 3px solid #ffc107; /* yellow border */
 91        background-color: #fff8e1;  /* soft yellow background */
 92        transition: background-color 0.3s ease, outline 0.3s ease;
 93    }
 94
 95    .active {
 96        background-color: #28a745; /* green background */
 97        color: #fff;
 98        box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
 99        transition: background-color 0.3s ease, box-shadow 0.3s ease;
100    }
101  </style>
102</head>
103<body>
104    <div class="container">
105        <h1>JavaScript Console</h1>
106        <button id="executeBtn">Execute</button>
107        <div id="output"></div>
108    </div>
109
110    <script>
111        // Override console.log to display messages in the #output element
112        (function () {
113            // Override console.log
114            const originalLog = console.log;
115            console.log = function (...args) {
116                originalLog.apply(console, args);
117                const message = document.createElement('div');
118                message.textContent = args
119                    .map(arg => (typeof arg === "object" && arg !== null ? JSON.stringify(arg) : String(arg)))
120                    .join(" ");
121                output.appendChild(message);
122            };
123
124            // Override console.error
125            const originalError = console.error;
126            console.error = function (...args) {
127                originalError.apply(console, args);
128                const message = document.createElement('div');
129                message.textContent = args
130                    .map(arg => (typeof arg === "object" && arg !== null ? JSON.stringify(arg) : String(arg)))
131                    .join(" ");
132                message.style.color = 'red'; // Color error messages red
133                output.appendChild(message);
134            };
135        })();
136
137        document.getElementById('executeBtn').addEventListener('click', () => {
138            // Prevent multiple loads
139            if (document.getElementById('externalScript')) return;
140
141            const script = document.createElement('script');
142            script.src = 'javascript-indexed-db.js';
143            script.id = 'externalScript';
144            //script.onload = () => console.log('javascript-indexed-db.js loaded and executed.');
145            //script.onerror = () => console.log('Failed to load javascript-indexed-db.js.');
146            document.body.appendChild(script);
147        });
148    </script>
149</body>
150</html>

JavaScript dan IndexedDB

IndexedDB adalah basis data penyimpanan key-value asinkron yang terintegrasi dalam browser. Itu menawarkan fitur yang mirip dengan basis data relasional dan memungkinkan Anda untuk menyimpan serta mencari sejumlah besar data terstruktur di sisi klien. Ini sangat berguna untuk aplikasi yang mendukung mode offline dan PWA (Progressive Web Apps).

Fitur-fitur dari IndexedDB

  • Beroperasi dengan cara asinkron dan berbasis event.
  • Objek JavaScript dapat disimpan di dalam object store.
  • Pencarian dengan query atau indeks dimungkinkan.
  • Ini memiliki kapasitas penyimpanan yang besar (ratusan MB atau lebih), memungkinkan Anda menyimpan lebih banyak data dibandingkan dengan cookies atau localStorage.

Membuka dan Membuat Database

Untuk menggunakan IndexedDB, Anda harus membuka database terlebih dahulu. Jika database belum ada, maka akan dibuat secara otomatis.

1const request = indexedDB.open('MyDatabase', 1); // Specify DB name and version
  • Metode open membuka database secara asinkron dan memicu tiga event berikut.

onsuccess

1// Fired when database is successfully opened
2request.onsuccess = (event) => {
3    const db = event.target.result; // Database instance
4    console.log('Database opened successfully:', db.name);
5};
  • Event onsuccess dipicu ketika database berhasil dibuka. Operasi selanjutnya harus dilakukan menggunakan request.result, yang akan tersedia pada titik ini.

onerror

1// Fired when database fails to open
2request.onerror = (event) => {
3    console.error('Failed to open database:', event.target.error);
4};
  • Event onerror dipicu ketika database gagal dibuka. Pencatatan log dan penanganan error harus dilakukan di sini.

onupgradeneeded

 1// Fired when database is newly created or upgraded
 2request.onupgradeneeded = (event) => {
 3    const db = event.target.result;
 4    console.log('Database upgrade needed (or newly created):', db.name);
 5
 6    // Example: Create an object store (like a table) if it doesn’t exist
 7    if (!db.objectStoreNames.contains('users')) {
 8        db.createObjectStore('users', { keyPath: 'id' });
 9        console.log('Object store "users" created');
10    }
11};
  • onupgradeneeded dipicu ketika database baru dibuat atau ketika versi yang ditentukan lebih tinggi dari versi saat ini. Membuat tabel (object store) dan mendefinisikan skema harus dilakukan pada saat ini.

Membuat Object Store

Pertama, buatlah 'object store' (setara dengan tabel) di dalam onupgradeneeded.

1request.onupgradeneeded = function (event) {
2    const db = event.target.result;
3    console.log("onupgradeneeded triggered. Database version:", db.version);
4    if (!db.objectStoreNames.contains('users')) {
5        console.log("Creating object store: users");
6        const store = db.createObjectStore('users', { keyPath: 'id' });
7        store.createIndex('name', 'name', { unique: false });
8    }
9};

Di sini, pengaturan berikut diterapkan:.

  • createObjectStore createObjectStore adalah metode untuk membuat object store baru di dalam database. Anda dapat menyimpan record dalam object store dan menentukan cara pengelolaan data dengan menetapkan keyPath serta opsi lainnya. Proses ini harus dilakukan di dalam event onupgradeneeded.

  • keyPath: 'id' Atur keyPath ke properti id, yang berarti setiap record diidentifikasi secara unik sebagai primary key. Hal ini memungkinkan id digunakan secara otomatis saat menambah, mencari, atau memperbarui data.

  • createIndex Gunakan metode createIndex untuk membuat indeks berdasarkan properti name untuk pencarian. Dengan mengatur unique: false, beberapa record dengan name yang sama diperbolehkan.

Menangani Koneksi Database yang Berhasil

Tetapkan proses yang akan dijalankan setelah koneksi database berhasil ke event onsuccess. Dalam proses ini, Anda mendapatkan instance database dan bersiap untuk membaca dan menulis data.

1request.onsuccess = function (event) {
2    const db = event.target.result;
3    console.log('Database opened successfully');
4    // Use db for reading and writing in subsequent operations
5};
  • Proses ini dijalankan ketika koneksi ke database IndexedDB berhasil.

  • event.target.result berisi instance database yang telah dibuka (objek IDBDatabase), yang digunakan untuk memulai transaksi dan mengakses object store.

  • Operasi baca-tulis seperti menambah, mengambil, memperbarui, dan menghapus data dilakukan menggunakan objek db.

Pada titik ini, database sudah siap sehingga Anda dapat mulai transaksi dengan aman.

Menambah Data

Berikut cara menambah data baru ke IndexedDB.

 1function addUser(db, user) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const request = store.add(user);
 5
 6    request.onsuccess = () => {
 7        console.log('User added:', user);
 8    };
 9
10    request.onerror = () => {
11        console.error('Add failed:', request.error);
12    };
13}
14
15// Example: Add a user
16request.onsuccess = function (event) {
17    const db = event.target.result;
18    addUser(db, { id: 1, name: 'Alice' });
19    addUser(db, { id: 2, name: 'Bob' });
20    addUser(db, { id: 3, name: 'John' });
21};
  • Buat transaksi dengan db.transaction() dan tentukan object store yang akan digunakan serta mode-nya (dalam kasus ini, readwrite).
  • Tambahkan data baru dengan menggunakan metode store.add().
  • Transaksi dikomit otomatis, tetapi jika Anda ingin mengelompokkan beberapa operasi, Anda dapat mengelolanya menggunakan event akhir transaksi.

Mengambil Data (Pencarian Berdasarkan Primary Key)

Berikut cara mengambil data spesifik menggunakan primary key.

 1function getUserById(db, id) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const request = store.get(id);
 5
 6    request.onsuccess = () => {
 7        if (request.result) {
 8            console.log('User found:', request.result);
 9        } else {
10            console.log('User not found');
11        }
12    };
13
14    request.onerror = () => {
15        console.error('Error retrieving user:', request.error);
16    };
17}
18
19// Example: Get a user by id
20request.onsuccess = function (event) {
21    const db = event.target.result;
22    getUserById(db, 1);
23};
  • Buat transaksi read-only dengan db.transaction().
  • Gunakan store.get(id) untuk mengambil data yang sesuai dengan primary key yang ditentukan.
  • onsuccess dipanggil saat pengambilan data berhasil, dan hasilnya akan ditampilkan jika tersedia. Jika tidak ada hasil, dianggap sebagai 'data yang sesuai tidak ditemukan.'.

Menggunakan Pencarian Indeks

Jika Anda ingin mencari berdasarkan properti selain primary key, gunakan indeks yang telah Anda buat sebelumnya.

 1function getUserByName(db, name) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const index = store.index('name');
 5    const request = index.get(name);
 6
 7    request.onsuccess = () => {
 8        if (request.result) {
 9            console.log('User by name:', request.result);
10        } else {
11            console.log('No user found with that name');
12        }
13    };
14
15    request.onerror = () => {
16        console.error('Error retrieving user by name:', request.error);
17    };
18}
19
20// Example: Get a user by id
21request.onsuccess = function (event) {
22    const db = event.target.result;
23    getUserByName(db, 'Alice');
24};
  • Akses indeks name yang sudah dibuat sebelumnya menggunakan store.index('name').
  • index.get(value) mengambil record pertama dengan nilai yang cocok. Jika beberapa record memiliki nilai yang sama, Anda dapat mengambil semuanya dengan index.getAll(value).

Memperbarui Data

Untuk memperbarui data yang sudah ada (overwrite), gunakan metode put().

Jika record dengan primary key yang cocok ada, maka akan diperbarui; jika tidak, record baru akan ditambahkan.

 1function updateUser(db, updatedUser) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const request = store.put(updatedUser);
 5
 6    request.onsuccess = () => {
 7        console.log('User updated:', updatedUser);
 8    };
 9
10    request.onerror = () => {
11        console.error('Update failed:', request.error);
12    };
13}
14
15// Example: Update user
16request.onsuccess = async (event) => {
17    const db = event.target.result;
18
19    // Test : update existing user
20    updateUser(db, { id: 3, name: 'John Updated' });
21
22    // Test : insert new user
23    updateUser(db, { id: 4, name: 'Charlie' });
24};
  • put() adalah metode yang praktis yang mendukung pembaruan sekaligus penambahan.
  • Jika data dengan primary key yang sama sudah ada, data tersebut akan ditimpa.
  • Jika Anda ingin memeriksa keberadaan data sebelum memperbarui, Anda dapat menggunakan get() untuk konfirmasi terlebih dahulu.

Menghapus Data

Untuk menghapus data yang sesuai dengan primary key tertentu, gunakan metode delete().

 1function deleteUser(db, id) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const request = store.delete(id);
 5
 6    request.onsuccess = () => {
 7        console.log(`User with id=${id} deleted successfully`);
 8    };
 9
10    request.onerror = () => {
11        console.error(`Failed to delete user with id=${id}:`, request.error);
12    };
13}
14
15// Example: Delete a user by id
16request.onsuccess = function (event) {
17    const db = event.target.result;
18    deleteUser(db, 4);
19};
  • Gunakan store.delete(id) untuk menghapus data dengan primary key yang cocok.
  • Perlu diketahui, meskipun data tidak ada, tidak akan terjadi error dan dianggap sebagai keberhasilan.
  • Menerapkan penanganan error akan membuat kode Anda lebih kuat.

Mengambil Semua Data

getAll()

Untuk mengambil semua record dalam object store, gunakan metode getAll().

 1function getAllUsers(db) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const request = store.getAll();
 5
 6    request.onsuccess = () => {
 7        console.log('All users:', request.result);
 8    };
 9
10    request.onerror = () => {
11        console.error('Failed to retrieve users:', request.error);
12    };
13}
14
15// Example: Get all users
16request.onsuccess = function (event) {
17    const db = event.target.result;
18    getAllUsers(db);
19};
  • getAll() mengambil semua record dalam object store yang ditentukan sebagai sebuah array.
  • Bahkan jika mengambil sejumlah besar data sekaligus, prosesnya tetap efisien.
  • Hasilnya disimpan sebagai array di request.result.
  • Selalu tambahkan penanganan error agar Anda dapat mengatasi kegagalan.

openCursor()

openCursor() adalah metode untuk menjelajah record di object store atau indeks secara berurutan. Ini berguna jika Anda ingin memproses data satu per satu daripada mengambil semuanya sekaligus.

 1function getAllUsersWithCursor(db) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const request = store.openCursor();
 5
 6    request.onsuccess = () => {
 7        const cursor = request.result;
 8        if (cursor) {
 9            console.log('User:', cursor.value); // Process the current record
10            cursor.continue(); // Move to the next record
11        } else {
12            console.log('All users have been processed.');
13        }
14    };
15
16    request.onerror = () => {
17        console.error('Failed to open cursor:', request.error);
18    };
19}
20
21// Example: Get all users
22request.onsuccess = function (event) {
23    const db = event.target.result;
24    getAllUsersWithCursor(db);
25};
  • Dengan menggunakan openCursor(), Anda memulai kursor dan mengambil semua record di object store satu per satu.
  • Gunakan cursor.value untuk mendapatkan objek data dari record yang sedang diakses.
  • Pindahkan kursor ke record berikutnya dengan cursor.continue().
  • Ketika cursor === null, semua record telah dijelajahi.

Contoh proses pembaruan menggunakan openCursor()

Sebagai contoh, proses mengubah nama pengguna yang name-nya adalah Alice menjadi Alicia akan seperti berikut:.

 1function updateUserName(db, oldName, newName) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const index = store.index('name'); // Use the 'name' index
 5    const request = index.openCursor(IDBKeyRange.only(oldName));
 6
 7    request.onsuccess = () => {
 8        const cursor = request.result;
 9        if (cursor) {
10            const user = cursor.value;
11            user.name = newName; // Update the name
12            const updateRequest = cursor.update(user);
13
14            updateRequest.onsuccess = () => {
15                console.log('Updated user:', user);
16            };
17
18            updateRequest.onerror = () => {
19                console.error('Failed to update user:', updateRequest.error);
20            };
21
22            cursor.continue();
23        } else {
24            console.log('All matching users have been updated.');
25        }
26    };
27
28    request.onerror = () => {
29        console.error('Cursor error:', request.error);
30    };
31}
32
33// Example: Update user name
34request.onsuccess = function (event) {
35    const db = event.target.result;
36    updateUserName(db, 'Alice', 'Alicia');
37};
  • IDBKeyRange.only(oldName) Dengan menggunakan IDBKeyRange.only, Anda dapat menargetkan hanya data yang kuncinya persis sama dengan oldName. Ini berguna ketika Anda ingin mengakses nilai tertentu secara langsung.

  • cursor.update() Setelah memperbarui cursor.value, memanggil update() akan menimpa data yang bersangkutan.

  • Menangani Kecocokan Ganda Dengan memanggil cursor.continue(), Anda dapat memindahkan kursor ke data berikutnya yang cocok. Ini memungkinkan Anda memproses beberapa data yang cocok dengan kunci atau kondisi yang sama secara berurutan.

  • Penanganan Kesalahan Dengan mencetak log pada onerror ketika sebuah proses gagal, akan lebih mudah untuk menyelidiki penyebab serta memperbaiki masalah saat operasional.

Contoh proses penghapusan menggunakan openCursor()

Sebagai contoh, proses menghapus semua pengguna yang name-nya adalah Bob akan seperti berikut:.

 1function deleteUsersByName(db, targetName) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const index = store.index('name');
 5    const request = index.openCursor(IDBKeyRange.only(targetName));
 6
 7    request.onsuccess = () => {
 8        const cursor = request.result;
 9        if (cursor) {
10            const deleteRequest = cursor.delete();
11
12            deleteRequest.onsuccess = () => {
13                console.log('Deleted user:', cursor.value);
14            };
15
16            deleteRequest.onerror = () => {
17                console.error('Failed to delete user:', deleteRequest.error);
18            };
19
20            cursor.continue();
21        } else {
22            console.log('All matching users have been deleted.');
23        }
24    };
25
26    request.onerror = () => {
27        console.error('Cursor error:', request.error);
28    };
29}
30
31// Example: Delete user by name
32request.onsuccess = function (event) {
33    const db = event.target.result;
34    deleteUsersByName(db, 'Bob');
35};
  • cursor.delete() Dengan cursor.delete(), data pada posisi kursor saat ini akan dihapus. Karena hasilnya dikembalikan secara asinkron, Anda dapat memeriksa prosesnya di onsuccess.

Catatan tentang Transaksi dan Pemrosesan Asinkron

IndexedDB bersifat asinkron dan berbasis event. Semua operasi harus ditangani dengan event onsuccess atau onerror. Ketika Anda ingin mengelompokkan beberapa proses sekaligus, akan lebih mudah jika membungkusnya dalam sebuah Promise.

 1function openDatabase() {
 2    return new Promise((resolve, reject) => {
 3        const request = indexedDB.open('MyDatabase', 1);
 4
 5        request.onupgradeneeded = function (event) {
 6            const db = event.target.result;
 7
 8            // Initialization process (creating object stores and indexes, etc.)
 9            const store = db.createObjectStore('users', { keyPath: 'id' });
10            store.createIndex('name', 'name', { unique: false });
11        };
12
13        request.onsuccess = function (event) {
14            const db = event.target.result;
15            resolve(db);
16        };
17
18        request.onerror = function () {
19            reject(request.error);
20        };
21    });
22}
23
24function addUserAsync(db, user) {
25    return new Promise((resolve, reject) => {
26        const transaction = db.transaction('users', 'readwrite');
27        const store = transaction.objectStore('users');
28        const request = store.add(user);
29
30        request.onsuccess = () => resolve();
31        request.onerror = () => reject(request.error);
32    });
33}
34
35async function main() {
36    try {
37        const db = await openDatabase();
38        await addUserAsync(db, { id: 1, name: 'Alice' });
39        console.log('User added successfully');
40    } catch (error) {
41        console.error('Error:', error);
42    }
43}
  • Membungkus fungsi seperti openDatabase atau addUserAsync dengan Promise memungkinkan Anda menangani proses asinkron dengan lebih intuitif menggunakan async/await.
  • Ini menghindari callback hell dan membuat kode lebih mudah dibaca.

Ringkasan

IndexedDB adalah fitur yang sangat kuat jika Anda ingin melakukan manajemen data yang lebih canggih di sisi browser. Pada awalnya, Anda mungkin akan bingung dengan pemrosesan asinkron berbasis event, tetapi setelah memahami strukturnya, Anda dapat melakukan operasi data secara penuh di sisi klien.

Khususnya, memperhatikan poin-poin berikut akan membantu Anda dalam menggunakannya dengan lebih lancar:.

  • Lakukan pengaturan awal dengan onupgradeneeded.
  • Perhatikan mode baca/tulis dari transaksi.
  • Indeks memungkinkan pencarian yang efisien.
  • Data dapat disimpan sebagai objek, sehingga sangat kompatibel dengan JSON.

Dengan menguasai IndexedDB, pengelolaan data untuk PWA dan aplikasi offline menjadi jauh lebih mudah.

Anda dapat mengikuti artikel di atas menggunakan Visual Studio Code di saluran YouTube kami. Silakan periksa juga saluran YouTube kami.

YouTube Video