IndexedDB: клиентская база данных для сложных приложений

Что такое IndexedDB и зачем он нужен?

IndexedDB — это мощная клиентская база данных, которая позволяет хранить большие объёмы структурированных данных прямо в браузере. В отличие от localStorage, она поддерживает транзакции, индексы и даже сложные запросы! 🎯

Когда использовать:

  • Для офлайн-режима в веб-приложениях 📱
  • Кеширования данных для ускорения работы 🚀
  • Хранения пользовательских настроек и истории 📊
  • Работы с большими файлами (например, изображения или аудио) 🖼️

🔥 Важно: IndexedDB — асинхронная API. Это значит, что операции не блокируют основной поток, и интерфейс остаётся отзывчивым.


Основные концепции IndexedDB

1. Базы данных (Databases)

Каждая база данных: - Существует в рамках одного источника (origin) - Имеет имя и версию - Содержит одно или несколько хранилищ объектов (object stores)

2. Хранилища объектов (Object Stores)

Это как таблицы в SQL: - Хранят данные в виде пар ключ-значение - Могут иметь индексы для быстрого поиска - Поддерживают различные типы ключей (числа, строки, даты)

3. Индексы

Позволяют: - Быстро искать данные по свойствам, отличным от первичного ключа - Создавать уникальные или неуникальные индексы - Сортировать данные по определённым полям


Открытие базы данных

Создадим или откроем базу данных "NotesApp" версии 1:

const openRequest = indexedDB.open('NotesApp', 1);

openRequest.onerror = function(event) {
  console.error('Database error:', event.target.error);
};

openRequest.onsuccess = function(event) {
  const db = event.target.result;
  console.log('Database opened successfully!');

  // Теперь можно работать с базой данных
};

openRequest.onupgradeneeded = function(event) {
  const db = event.target.result;

  // Создаём хранилище объектов для заметок
  const notesStore = db.createObjectStore('notes', { 
    keyPath: 'id',
    autoIncrement: true 
  });

  // Создаём индекс для поиска по заголовку
  notesStore.createIndex('by_title', 'title', { unique: false });

  console.log('Database structure updated!');
};

💡 Совет: Версия базы данных важна! При её изменении срабатывает onupgradeneeded, где можно обновить схему данных.


CRUD-операции в IndexedDB

Создание записи (Create)

function addNote(db, note) {
  const transaction = db.transaction('notes', 'readwrite');
  const store = transaction.objectStore('notes');

  const request = store.add(note);

  request.onsuccess = function() {
    console.log('Note added with ID:', request.result);
  };

  request.onerror = function(event) {
    console.error('Error adding note:', event.target.error);
  };
}

Чтение записи (Read)

function getNote(db, id) {
  return new Promise((resolve, reject) => {
    const transaction = db.transaction('notes', 'readonly');
    const store = transaction.objectStore('notes');

    const request = store.get(id);

    request.onsuccess = function() {
      resolve(request.result);
    };

    request.onerror = function(event) {
      reject(event.target.error);
    };
  });
}

Обновление записи (Update)

function updateNote(db, id, updates) {
  const transaction = db.transaction('notes', 'readwrite');
  const store = transaction.objectStore('notes');

  // Сначала получаем запись
  const getRequest = store.get(id);

  getRequest.onsuccess = function() {
    const note = getRequest.result;
    if (!note) return;

    // Обновляем поля
    Object.assign(note, updates);

    // Сохраняем обратно
    const putRequest = store.put(note);

    putRequest.onsuccess = function() {
      console.log('Note updated successfully');
    };
  };
}

Удаление записи (Delete)

function deleteNote(db, id) {
  const transaction = db.transaction('notes', 'readwrite');
  const store = transaction.objectStore('notes');

  const request = store.delete(id);

  request.onsuccess = function() {
    console.log('Note deleted');
  };

  request.onerror = function(event) {
    console.error('Error deleting note:', event.target.error);
  };
}

Работа с индексами и сложные запросы

Поиск по индексу

function searchByTitle(db, title) {
  return new Promise((resolve, reject) => {
    const transaction = db.transaction('notes', 'readonly');
    const store = transaction.objectStore('notes');
    const index = store.index('by_title');

    const request = index.getAll(title);

    request.onsuccess = function() {
      resolve(request.result);
    };

    request.onerror = function(event) {
      reject(event.target.error);
    };
  });
}

Получение всех записей

function getAllNotes(db) {
  return new Promise((resolve, reject) => {
    const transaction = db.transaction('notes', 'readonly');
    const store = transaction.objectStore('notes');

    const request = store.getAll();

    request.onsuccess = function() {
      resolve(request.result);
    };

    request.onerror = function(event) {
      reject(event.target.error);
    };
  });
}

Использование курсоров

function iterateNotes(db, callback) {
  const transaction = db.transaction('notes', 'readonly');
  const store = transaction.objectStore('notes');

  const request = store.openCursor();

  request.onsuccess = function(event) {
    const cursor = event.target.result;
    if (cursor) {
      callback(cursor.value);
      cursor.continue();
    }
  };
}

Лучшие практики работы с IndexedDB

  1. Всегда закрывайте соединения
    Когда закончили работу, вызовите db.close(). Это освобождает ресурсы.

  2. Используйте транзакции правильно
    Транзакции завершаются автоматически после обработки всех запросов. Не делайте между запросами долгих операций.

  3. Обрабатывайте ошибки
    IndexedDB операции могут завершиться неудачно. Всегда добавляйте обработчики onerror.

  4. Оптимизируйте структуру данных
    Продумывайте индексы заранее — их нельзя изменить без обновления версии базы.

  5. Используйте промисы или async/await
    Оберните операции в промисы для удобства работы:

async function getNoteAsync(db, id) {
  const transaction = db.transaction('notes', 'readonly');
  const store = transaction.objectStore('notes');

  return new Promise((resolve, reject) => {
    const request = store.get(id);
    request.onsuccess = () => resolve(request.result);
    request.onerror = (event) => reject(event.target.error);
  });
}

Когда IndexedDB — не лучший выбор?

Несмотря на мощь, IndexedDB подходит не для всех сценариев:

  • Для простых данных — используйте localStorage или sessionStorage
  • Для работы с сервером — лучше подойдут серверные базы данных
  • Для мультимедиа — иногда проще использовать Cache API

Но для сложных клиентских приложений, требующих офлайн-работы и структурированного хранения данных, IndexedDB — идеальное решение! 🎉

Скрыть рекламу навсегда

🧠 Учёба без воды и зубрёжки

Закрытый Boosty с наработками опытного преподавателя.

Объясняю сложное так, чтобы щелкнуло.

🚀 Забрать доступ к Boosty