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

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
Автор
Редакция

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

Обзор Nothing Launcher — установка и отзывы
Обзоры

Обзор Nothing Launcher — установка и отзывы

Каррирование функций в JavaScript
JavaScript

Каррирование функций в JavaScript

Открытый драйвер AMD для Linux — как включить
Linux

Открытый драйвер AMD для Linux — как включить

Как смотреть WWDC 2022 в прямом эфире
Технологии

Как смотреть WWDC 2022 в прямом эфире

Как включить Face ID с маской на iPhone
Руководство

Как включить Face ID с маской на iPhone

Как смотреть презентацию iPhone 13 — 14 сентября
Новости

Как смотреть презентацию iPhone 13 — 14 сентября