Гид по технологиям

IndexedDB: руководство по работе в браузере

6 min read Web‑storage Обновлено 29 Dec 2025
IndexedDB в браузере: полное руководство
IndexedDB в браузере: полное руководство

Коридор между рядами серверных стоек за стеклянными дверями.

Что такое IndexedDB

IndexedDB — это встроенная в браузер транзакционная клиентская база данных (NoSQL). Она хранит сложные объекты (JSON), поддерживает индексы, транзакции и оффлайн‑доступ. В отличие от localStorage, IndexedDB рассчитана на большие объёмы данных, асинхронные операции и более гибкие схемы доступа.

Ключевые свойства в одной строке:

  • NoSQL: храним объекты, а не таблицы с жёсткой схемой.
  • Транзакции: группировка операций с откатом при ошибке.
  • Индексы: быстрый поиск и сортировка по полям.
  • Оффлайн: данные доступны без сети.

Настройка базы данных

Чтобы создать или открыть базу, используйте indexedDB.open(name, version). Метод возвращает объект IDBOpenDBRequest, на котором нужно слушать события: success, error и upgradeneeded. Версия позволяет обновлять структуру базы и триггерит событие upgradeneeded.

Пример: открыть базу usersdb версии 1:

const openRequest = indexedDB.open("usersdb", 1);

openRequest.onsuccess = function (event) {
  const db = event.target.result;
  console.log("Database opened", db);
};

openRequest.onerror = function (event) {
  console.error("Open error", event.target.error);
};

openRequest.onupgradeneeded = function (event) {
  const db = event.target.result;
  // Здесь создаём object store и индексы (см. раздел ниже)
};

Важно: upgradeneeded выполняется только при создании базы или при повышении версии. Все изменения схемы (создание/удаление object store или индексов) нужно делать внутри обработчика upgradeneeded.

Создание объекта‑хранилища (object store)

Object store в IndexedDB похож на таблицу, но хранит произвольные объекты. При создании указывают ключ (keyPath) или включают автоинкремент.

Пример создания object store с автоинкрементируемым ключом id:

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

  const userObjectStore = db.createObjectStore("userStore", {
    keyPath: "id",
    autoIncrement: true,
  });

  // Создание индексов
  userObjectStore.createIndex("name", "name", { unique: false });
  userObjectStore.createIndex("email", "email", { unique: true });
};

Пояснения:

  • keyPath — путь к свойству объекта, которое используется как первичный ключ.
  • autoIncrement — если true и ключ отсутствует, браузер сгенерирует числовой ключ.
  • Удаление или изменение object store нельзя делать в обычных success‑обработчиках; только в upgradeneeded.

Индексы и поиск

Индекс ускоряет поиск по полю. Индекс создаётся методом createIndex(name, keyPath, options). Если индекс уникальный (unique: true), попытка добавления дубликата вызовет ошибку.

Пример создания индексов (см. выше). Для выполнения поиска по индексу используйте индекс через objectStore.index(name):

const transaction = db.transaction("userStore", "readonly");
const store = transaction.objectStore("userStore");
const index = store.index("email");

const request = index.get("user@example.com");
request.onsuccess = function () {
  console.log("User by email:", request.result);
};

Используйте openCursor/getAll для выборок с диапазоном или постраничной загрузки.

Добавление данных

Все операции на запись должны выполняться в транзакции с режимом “readwrite”. Для добавления используйте add() (если хотите ошибку при совпадении ключа) или put() (если хотите заменить существующую запись).

Пример функции добавления:

const addUserData = (userData, db) => {
  const transaction = db.transaction("userStore", "readwrite");
  const userObjectStore = transaction.objectStore("userStore");

  const request = userObjectStore.add(userData);

  request.onsuccess = function () {
    console.log("User added", request.result);
  };

  request.onerror = function (event) {
    console.error("Add error", event.target.error);
  };
};

Советы:

  • Проверяйте, что данные валидны до вызова add/put (валидация на уровне приложения).
  • Используйте индексы для поиска перед добавлением, если требуется уникальность по составному набору полей.

Получение данных

Для получения отдельных объектов используйте get(key), для всех — getAll([query]).

Пример получения по id:

const getUserData = (id, db) => {
  const transaction = db.transaction("userStore", "readonly");
  const userObjectStore = transaction.objectStore("userStore");

  const request = userObjectStore.get(id);

  request.onsuccess = function () {
    console.log(request.result);
  };

  request.onerror = function (event) {
    console.error("Get error", event.target.error);
  };
};

Для диапазонных запросов используйте IDBKeyRange и курсоры:

const range = IDBKeyRange.bound(10, 20); // ключи от 10 до 20
const cursorRequest = userObjectStore.openCursor(range);
cursorRequest.onsuccess = function (event) {
  const cursor = event.target.result;
  if (cursor) {
    console.log(cursor.key, cursor.value);
    cursor.continue();
  }
};

Обновление данных

Алгоритм: открыть транзакцию readwrite, получить объект через get(), изменить поля, записать назад через put(). put() вставит объект, если ключа нет, или заменит существующий.

Пример:

const updateUserData = (id, newUserData, db) => {
  const transaction = db.transaction("userStore", "readwrite");
  const store = transaction.objectStore("userStore");

  const getRequest = store.get(id);
  getRequest.onsuccess = function (event) {
    const user = event.target.result;
    if (!user) {
      console.warn("User not found", id);
      return;
    }

    // Обновляем поля
    user.name = newUserData.name || user.name;
    user.email = newUserData.email || user.email;

    const putRequest = store.put(user);
    putRequest.onsuccess = function () {
      console.log("User updated");
    };
    putRequest.onerror = function (e) {
      console.error("Put error", e.target.error);
    };
  };
  getRequest.onerror = function (e) {
    console.error("Get for update error", e.target.error);
  };
};

Удаление данных

Удаление выполняется через delete(key) в транзакции readwrite.

const deleteUserData = (id, db) => {
  const transaction = db.transaction("userStore", "readwrite");
  const store = transaction.objectStore("userStore");

  const request = store.delete(id);
  request.onsuccess = function () {
    console.log("User deleted", id);
  };
  request.onerror = function (e) {
    console.error("Delete error", e.target.error);
  };
};

Транзакции и обработка ошибок

Транзакция автоматически закрывается при завершении всех запросов. Если один из запросов вызвал ошибку и не был обработан, транзакция может откатиться. Подписывайтесь на transaction.oncomplete, transaction.onerror, transaction.onabort для логики завершения/отката.

Пример отслеживания транзакции:

const tx = db.transaction("userStore", "readwrite");
const store = tx.objectStore("userStore");

tx.oncomplete = () => console.log("Transaction complete");
tx.onerror = (e) => console.error("Transaction error", e.target.error);
tx.onabort = () => console.warn("Transaction aborted");

Когда использовать IndexedDB, а когда localStorage

Используйте localStorage, если:

  • Нужны простые пары «ключ‑значение» строкового типа.
  • Объём данных очень мал и не требует индексации.
  • Нужна синхронная блокирующая API (обычно не рекомендуется).

Используйте IndexedDB, если:

  • Нужны большие объёмы данных (мультимедиа, каталоги, кеши).
  • Требуется поиск, сортировка и индексация по полям.
  • Нужна транзакционная целостность.

Краткая матрица сравнения:

СвойствоlocalStorageIndexedDB
Асинхронностьнет (синхронный)да (асинхронный)
Объём данныхнебольшойбольшой
Индексынетесть
Транзакциинетесть

Примеры отказов и ограничения

Когда IndexedDB может подвести:

  • Ограничения квот браузера: в некоторых окружениях (мобильные браузеры, private mode) объём хранения ограничен или отсутствует.
  • Совместимость: старые браузеры могут не поддерживать некоторые возможности (но базовый IndexedDB широкий).
  • Сложность: асинхронная модель и обработка версий требует аккуратной архитектуры.

Контрпример: для хранения простого флага “tema=dark” localStorage проще и надёжнее.

Альтернативные подходы

  • Service Worker + Cache API — подходит для кеширования HTTP‑ресурсов.
  • Web SQL (устаревший) — не рекомендуется.
  • Использование поверх IndexedDB библиотек (localForage, Dexie.js, idb) — облегчают работу с API и управлением версиями.

Совет: для производственных проектов часто используют обёртки (Dexie, idb) — они предоставляют промисы, удобные API и миграции.

Модель мышления и эвристики

  • Думайте в терминах объектов, а не таблиц: храните сущности как JSON.
  • Разделяйте данные по object store для логической изоляции и производительности.
  • Проектируйте индексы только для полей, по которым реально будете искать.
  • Всегда планируйте миграции схемы (upgradeneeded) с учётом отката.

Миграции и совместимость версий

  • Любые изменения структуры (создание/удаление object store, индексов) выполняйте в upgradeneeded.
  • Пишите idempotent‑миграции: проверяйте наличие store/index перед созданием.

Пример безопасной миграции:

openRequest.onupgradeneeded = function (event) {
  const db = event.target.result;
  if (!db.objectStoreNames.contains("userStore")) {
    const store = db.createObjectStore("userStore", { keyPath: "id", autoIncrement: true });
    store.createIndex("email", "email", { unique: true });
  }
};

Критерии приёмки

  1. База успешно открывается и инициализируется при запуске.
  2. В upgradeneeded корректно создаются все необходимые object store и индексы.
  3. Операции CRUD выполняются без необработанных ошибок и возвращают ожидаемые результаты.
  4. Транзакции завершаются (oncomplete) или корректно откатываются (onabort) при ошибках.
  5. Тесты покрывают миграции (включая повторный вызов upgradeneeded с increment версии).

Шпаргалка: часто используемые операции

  • Открыть базу: indexedDB.open(name, version)
  • Создать object store: db.createObjectStore(name, {keyPath, autoIncrement})
  • Создать индекс: store.createIndex(name, keyPath, options)
  • Транзакция: db.transaction(storeName, “readwrite”|”readonly”)
  • Добавить: store.add(obj)
  • Обновить: store.put(obj)
  • Получить по ключу: store.get(key)
  • Получить все: store.getAll()
  • Удалить: store.delete(key)
  • Курсор: store.openCursor(range)

Безопасность и соответствие (GDPR)

  • Данные в IndexedDB хранятся локально в устройстве пользователя. При хранении персональных данных соблюдайте принципы минимизации и оповещайте пользователей о хранении данных.
  • Реализуйте шифрование на уровне приложения, если в базе хранится чувствительная информация (например, токены, персональные данные). Браузерное хранилище само по себе не шифрует данные.
  • Предусмотрите механизм удаления данных по запросу пользователя (право на удаление). Удаление должно корректно выполняться и подтверждаться.
  • Документируйте, какие данные и где хранятся, чтобы соответствовать требованиям прозрачности.

Чек‑лист по внедрению (роли)

Разработчик:

  • Реализовал open/upgradeneeded с миграциями.
  • Обработал success/error для всех запросов.
  • Написал тесты CRUD и миграций.

Тестировщик:

  • Проверил добавление/обновление/удаление/чтение.
  • Смоделировал ошибки транзакций и подтвердил откат.

Менеджер продукта:

  • Подтвердил, что данные, хранящиеся локально, соответствуют политике конфиденциальности.
  • Определил требования к объёму и срокам хранения.

Инцидентный сценарий и восстановление

Если данные повреждены или миграция прошла некорректно:

  1. Локализуйте проблему: просмотрите консоль браузера и логи onerror/onabort.
  2. Если возможно, откатите фронтенд на предыдущую версию, ожидающую старой схемы.
  3. Реализуйте idempotent‑миграцию, которая корректно преобразует существующие записи.
  4. Сообщите пользователю о возможной потере локальных данных и предложите опции (например, сброс локального кэша).
  5. Обновите тесты миграций и CI, чтобы предотвратить повторение.

Тесты и критерии приёмки

  • Unit: мокировать indexedDB или использовать in‑memory реализацию (например, fakeIndexedDB) для тестирования CRUD.
  • Integration: прогонять реальные операции в контролируемом окружении браузера.
  • Acceptance: при успешной миграции версии данные доступны и консистентны; при ошибке транзакция откатывается.

Короткое резюме

IndexedDB — мощный инструмент для клиентского хранения структурированных и больших данных. Он поддерживает транзакции, индексы и оффлайн‑доступ, но требует аккуратности при проектировании схемы и миграций. Для упрощения разработки рассмотрите использование проверенных библиотек‑обёрток.

Важно: чаще всего в реальных проектах используют библиотеку‑абстракцию (Dexie, idb или localForage) для удобства и устойчивости.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Пересылка почты Outlook ↔ Gmail: полное руководство
Почта

Пересылка почты Outlook ↔ Gmail: полное руководство

Как узнать, что пора менять батарейку AirTag
Гаджеты

Как узнать, что пора менять батарейку AirTag

Как удалить устройства из Google Home
Умный дом

Как удалить устройства из Google Home

Вернуть «Open command window here» в Windows 11
Windows

Вернуть «Open command window here» в Windows 11

Подключение Bluetooth-наушников к Wear OS
Гаджеты

Подключение Bluetooth-наушников к Wear OS

Запустить успешную страницу на Patreon
Монетизация

Запустить успешную страницу на Patreon