Hướng Dẫn Dành Cho Người Mới Bắt Đầu Về Việc Sử Dụng API IndexedDB

中村 拓也

中村 拓也

27 tháng 3 2025

Hướng Dẫn Dành Cho Người Mới Bắt Đầu Về Việc Sử Dụng API IndexedDB

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ó.

💡
Khi bạn cũng đang làm việc với các API, điều quan trọng là phải xem xét cách các công cụ như Apidog có thể tinh giản quy trình làm việc của bạn. Apidog cung cấp một nền tảng thống nhất giúp đơn giản hóa thiết kế API, kiểm tra và tài liệu. Khác với các công cụ khác yêu cầu nhiều bước riêng biệt để quản lý API, Apidog tập hợp mọi thứ lại một chỗ với các tính năng như đồng bộ thời gian thực và hợp tác liền mạch cho các nhóm phát triển.
button

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()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.

button

Thực hành thiết kế API trong Apidog

Khám phá cách dễ dàng hơn để xây dựng và sử dụng API