Xây dựng các ứng dụng hoạt động liền mạch khi không có kết nối đã trở thành một yêu cầu chính trong phát triển web hiện đại. IndexedDB cung cấp một giải pháp mạnh mẽ cho các nhà phát triển đang tìm cách lưu trữ và quản lý một khối lượng lớn dữ liệu trực tiếp trong trình duyệt. Cơ sở dữ liệu NoSQL phía khách này cho phép xử lý dữ liệu hiệu quả, khiến nó trở nên lý tưởng cho các ứng dụng web cần chức năng offline. Trong bài viết này, chúng ta sẽ khám phá những điều cơ bản về IndexedDB, các tính năng chính của nó và hướng dẫn bạn thực hiện các bước thiết lập và sử dụng trong các dự án web của bạn.
IndexedDB là gì?
IndexedDB là một cơ sở dữ liệu NoSQL phía khách mạnh mẽ cho phép các ứng dụng web lưu trữ và quản lý dữ liệu có cấu trúc trực tiếp trong trình duyệt. Nó cung cấp một giải pháp hiệu quả cho việc xử lý các tập dữ liệu lớn tại chỗ, khiến cho nó trở nên lý tưởng cho chức năng offline và các ứng dụng nặng về dữ liệu.

Một trong những lợi thế chính của IndexedDB là khả năng hỗ trợ các ứng dụng web offline. Nó cho phép các nhà phát triển lưu trữ một lượng lớn dữ liệu, sử dụng chỉ mục để truy xuất nhanh, quản lý giao dịch và điều hướng dữ liệu bằng con trỏ. Những tính năng này khiến nó trở thành sự lựa chọn tuyệt vời cho việc xử lý dữ liệu phức tạp trong các ứng dụng web.
Hầu hết các trình duyệt web hiện đại, bao gồm Chrome, Firefox, Safari và Edge, đều hỗ trợ IndexedDB. Tuy nhiên, điều quan trọng là đảm bảo phiên bản của IndexedDB tương thích với trình duyệt mà bạn đang nhắm đến cho ứng dụng web của mình.
Để bắt đầu sử dụng IndexedDB, bước đầu tiên là mở một kết nối đến cơ sở dữ liệu, điều này cần thiết để tạo và tương tác với nó.

Bắt đầu với IndexedDB: Hướng dẫn toàn diện
IndexedDB là một giải pháp lưu trữ phía khách mạnh mẽ cho các ứng dụng web. Hướng dẫn này sẽ dẫn bạn qua mọi thứ từ thiết lập cơ bản đến các kỹ thuật nâng cao và triển khai thực tế.
Thiết lập IndexedDB
Tạo và mở cơ sở dữ liệu
Để tạo một cơ sở dữ liệu mới trong IndexedDB, bạn sẽ sử dụng phương thức indexedDB.open()
:
const request = indexedDB.open("MyDatabase", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Tạo các kho đối tượng và chỉ mục ở đây
};
Phương thức này nhận hai tham số: tên cơ sở dữ liệu và số phiên bản. Để mở một cơ sở dữ liệu đã tồn tại, bạn chỉ cần gọi phương thức mà không chỉ định số phiên bản.
Phiên bản cơ sở dữ liệu
IndexedDB hỗ trợ phiên bản để xử lý các thay đổi cấu trúc. Khi bạn mở một cơ sở dữ liệu với số phiên bản cao hơn số hiện có, sự kiện onupgradeneeded
sẽ được kích hoạt, cho phép bạn cập nhật cấu trúc cơ sở dữ liệu của mình.
Tạo kho đối tượng và chỉ mục
Các kho đối tượng là những container cho dữ liệu của bạn trong IndexedDB:
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Tạo một kho đối tượng với đường dẫn khóa
const objectStore = db.createObjectStore("customers", { keyPath: "id" });
// Tạo một chỉ mục cho việc truy vấn hiệu quả
objectStore.createIndex("name", "name", { unique: false });
};
Làm việc với giao dịch
Hiểu các nguyên tắc cơ bản về giao dịch
Các giao dịch trong IndexedDB duy trì tính toàn vẹn của dữ liệu bằng cách nhóm nhiều hoạt động thành một đơn vị nguyên tử. Chúng đảm bảo rằng hoặc tất cả các thay đổi được áp dụng hoặc không có thay đổi nào.
Tạo và quản lý giao dịch
const transaction = db.transaction(["customers"], "readwrite");
const objectStore = transaction.objectStore("customers");
transaction.oncomplete = (event) => {
console.log("Giao dịch hoàn thành thành công");
};
transaction.onerror = (event) => {
console.error("Giao dịch thất bại");
};
Xử lý lỗi và quay lại
Nếu một hoạt động thất bại, bạn có thể sử dụng phương thức abort()
để quay lại toàn bộ giao dịch:
try {
// Thực hiện các hoạt động
} catch (error) {
transaction.abort();
console.error("Giao dịch đã bị hủy bỏ:", error);
}
Các hoạt động dữ liệu trong IndexedDB
Thêm dữ liệu
const customerData = { id: "00001", name: "John Doe", email: "john@example.com" };
const request = objectStore.add(customerData);
request.onsuccess = (event) => {
console.log("Dữ liệu đã được thêm thành công");
};
Truy xuất dữ liệu
Truy xuất dữ liệu cơ bản theo khóa:
const request = objectStore.get("00001");
request.onsuccess = (event) => {
console.log("Dữ liệu khách hàng:", request.result);
};
Lọc với chỉ mục
const index = objectStore.index("name");
const request = index.get("John Doe");
request.onsuccess = (event) => {
console.log("Tìm thấy theo tên:", request.result);
};
Các phương pháp truy vấn nâng cao
Đối với các truy vấn phức tạp, IndexedDB cung cấp các truy vấn theo phạm vi và truy vấn phức hợp bằng cách sử dụng các phương thức như openCursor()
và IDBKeyRange
:
const range = IDBKeyRange.bound("A", "F"); // Các tên bắt đầu bằng A đến F
const request = index.openCursor(range);
Cập nhật hồ sơ
const updateData = { id: "00001", name: "John Smith", email: "john@example.com" };
const request = objectStore.put(updateData);
Xóa hồ sơ
const request = objectStore.delete("00001");
request.onsuccess = (event) => {
console.log("Hồ sơ đã được xóa");
};
Làm việc với con trỏ
Hiểu chức năng của con trỏ
Các con trỏ cho phép bạn dễ dàng lặp qua các hồ sơ trong một kho đối tượng hoặc chỉ mục, cung cấp một cách để duyệt và thao tác dữ liệu.
Duyệt qua các hồ sơ
const request = objectStore.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
console.log("Khóa:", cursor.key, "Giá trị:", cursor.value);
cursor.continue(); // Di chuyển đến hồ sơ tiếp theo
} else {
console.log("Không còn hồ sơ nào");
}
};
Thay đổi dữ liệu bằng cách sử dụng con trỏ
const transaction = db.transaction(["customers"], "readwrite");
const objectStore = transaction.objectStore("customers");
const request = objectStore.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
if (cursor.value.status === "inactive") {
const updateData = cursor.value;
updateData.status = "active";
cursor.update(updateData);
}
cursor.continue();
}
};
Quản lý cấu trúc và nâng cấp
Nâng cấp cấu trúc cơ sở dữ liệu
Khi ứng dụng của bạn phát triển, bạn có thể cần sửa đổi cấu trúc cơ sở dữ liệu của mình:
const request = indexedDB.open("MyDatabase", 2); // Tăng số phiên bản
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Kiểm tra xem kho đối tượng có tồn tại không
if (!db.objectStoreNames.contains("newStore")) {
db.createObjectStore("newStore", { keyPath: "id" });
}
};
Di chuyển dữ liệu trong quá trình nâng cấp
request.onupgradeneeded = (event) => {
const db = event.target.result;
const oldVersion = event.oldVersion;
if (oldVersion < 1) {
// Thiết lập phiên bản đầu tiên
}
if (oldVersion < 2) {
// Di chuyển dữ liệu sang cấu trúc mới
const transaction = event.target.transaction;
const oldStore = transaction.objectStore("oldStore");
const newStore = db.createObjectStore("newStore", { keyPath: "id" });
oldStore.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
newStore.add(cursor.value);
cursor.continue();
}
};
}
};
Tối ưu hóa hiệu suất
Các hoạt động hàng loạt hiệu quả
Để có hiệu suất tốt hơn, hãy sử dụng các hoạt động hàng loạt khi làm việc với nhiều hồ sơ:
const transaction = db.transaction(["customers"], "readwrite");
const store = transaction.objectStore("customers");
// Thêm nhiều hồ sơ trong một giao dịch duy nhất
customerList.forEach(customer => {
store.add(customer);
});
Tận dụng các chỉ mục cho các truy vấn nhanh hơn
Tạo chỉ mục trên các thuộc tính thường được truy vấn để đảm bảo việc truy xuất dữ liệu nhanh hơn:
objectStore.createIndex("email", "email", { unique: true });
objectStore.createIndex("lastLogin", "lastLogin", { unique: false });
Các thực tiễn tốt nhất trong quản lý kết nối
Mở kết nối chỉ khi cần thiết và đóng chúng khi hoàn tất:
let db;
function openDB() {
const request = indexedDB.open("MyDatabase", 1);
request.onsuccess = (event) => {
db = event.target.result;
};
return request;
}
// Khi bạn đã hoàn tất với cơ sở dữ liệu
function closeDB() {
if (db) {
db.close();
db = null;
}
}
Ví dụ thực tế: Quản lý tác vụ với hỗ trợ offline
Thiết lập cơ sở dữ liệu
const request = indexedDB.open("TaskManagerDB", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
const taskStore = db.createObjectStore("tasks", { keyPath: "id", autoIncrement: true });
// Tạo các chỉ mục để truy vấn
taskStore.createIndex("status", "status", { unique: false });
taskStore.createIndex("dueDate", "dueDate", { unique: false });
};
Thêm tác vụ
function addTask(taskData) {
const transaction = db.transaction(["tasks"], "readwrite");
const taskStore = transaction.objectStore("tasks");
return new Promise((resolve, reject) => {
const request = taskStore.add({
title: taskData.title,
description: taskData.description,
status: "pending",
dueDate: taskData.dueDate,
createdAt: new Date()
});
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
Truy xuất và hiển thị tác vụ
function getAllTasks() {
const transaction = db.transaction(["tasks"], "readonly");
const taskStore = transaction.objectStore("tasks");
return new Promise((resolve, reject) => {
const request = taskStore.openCursor();
const tasks = [];
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
tasks.push(cursor.value);
cursor.continue();
} else {
resolve(tasks);
}
};
request.onerror = () => reject(request.error);
});
}
Cập nhật và xóa tác vụ
function updateTaskStatus(id, newStatus) {
const transaction = db.transaction(["tasks"], "readwrite");
const taskStore = transaction.objectStore("tasks");
return new Promise((resolve, reject) => {
const getRequest = taskStore.get(id);
getRequest.onsuccess = () => {
const task = getRequest.result;
task.status = newStatus;
task.updatedAt = new Date();
const updateRequest = taskStore.put(task);
updateRequest.onsuccess = () => resolve(true);
updateRequest.onerror = () => reject(updateRequest.error);
};
getRequest.onerror = () => reject(getRequest.error);
});
}
Đồng bộ với máy chủ
function syncWithServer() {
if (!navigator.onLine) {
return Promise.reject(new Error("Không có kết nối internet"));
}
return getAllTasks()
.then(tasks => {
// Lọc các tác vụ cần đồng bộ
const unsynced = tasks.filter(task => !task.synced);
// Gửi đến máy chủ bằng API fetch
return fetch('https://api.example.com/tasks/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(unsynced)
});
})
.then(response => response.json())
.then(result => {
// Đánh dấu các tác vụ là đã đồng bộ
const transaction = db.transaction(["tasks"], "readwrite");
const taskStore = transaction.objectStore("tasks");
result.syncedIds.forEach(id => {
const request = taskStore.get(id);
request.onsuccess = () => {
const task = request.result;
task.synced = true;
taskStore.put(task);
};
});
return result;
});
}
// Lắng nghe các sự kiện trực tuyến để đồng bộ tự động
window.addEventListener('online', syncWithServer);
Tích hợp Apidog để quản lý API với IndexedDB
Khi xây dựng các ứng dụng với IndexedDB để lưu trữ phía khách, bạn thường cần tương tác với các API phía máy chủ để đồng bộ dữ liệu. Apidog cung cấp một giải pháp liền mạch cho việc quản lý những tương tác API này.

Tại sao Apidog cải thiện phát triển IndexedDB
Khi bạn phát triển các ứng dụng có khả năng offline với IndexedDB, Apidog cung cấp một số lợi ích:
Đồng bộ thời gian thực: Apidog đảm bảo các điểm cuối API của bạn được sử dụng cho việc đồng bộ dữ liệu luôn được cấu hình và kiểm tra đúng cách, loại bỏ các vấn đề tích hợp khi ứng dụng của bạn trực tuyến.
Các phản hồi API giả lập: Khi phát triển chức năng offline, các công cụ giả lập thông minh của Apidog cho phép bạn mô phỏng các phản hồi API để thử nghiệm logic đồng bộ IndexedDB của bạn mà không cần máy chủ trực tiếp.

Thiết kế API hợp tác: Khi nhóm của bạn đang cùng lúc làm việc trên lưu trữ phía trước và các API phía máy chủ, Apidog tạo điều kiện cho việc hợp tác thời gian thực trên các thông số kỹ thuật API.

Bằng cách tích hợp Apidog vào quy trình phát triển của bạn, bạn tạo ra một cầu nối liền mạch giữa lưu trữ phía khách và xử lý phía máy chủ, làm cho ứng dụng của bạn trở nên mạnh mẽ hơn và dễ bảo trì hơn.