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

Как использовать IndexedDB в браузере: руководство и лучшие практики

7 min read Веб-разработка Обновлено 09 Jan 2026
IndexedDB в браузере: руководство и лучшие практики
IndexedDB в браузере: руководство и лучшие практики

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

Что такое IndexedDB

IndexedDB — это клиентская NoSQL-база данных, встроенная в веб-браузеры, которая позволяет сохранять и извлекать структурированные объекты (ключ–значение) на стороне пользователя. Ключевая особенность — поддержка транзакций, индексов и больших объёмов данных, а также работа в офлайне.

Краткое определение: IndexedDB — асинхронная, транзакционная база данных в браузере для хранения объектов и индексированных запросов.

Важно: IndexedDB — не замена серверной БД. Она хранит данные локально у пользователя и управляется в пределах домена/происхождения (origin).

Преимущества и когда не использовать

Преимущества:

  • Поддержка больших объёмов данных и бинарных объектов (Blob, ArrayBuffer).
  • Асинхронный API, не блокирует главный поток (в отличие от sync localStorage в старых реализациях).
  • Транзакции гарантируют согласованность при нескольких операциях.
  • Индексы позволяют эффективнее выполнять поиск и сортировку.

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

  • Для простых, коротких настроек или токенов, где достаточно localStorage или sessionStorage.
  • Если требуется синхронный доступ к данным из нескольких вкладок без периодической синхронизации (в этом случае дополнительно нужны механизмы синхронизации).
  • Если данные должны быть централизованно доступны и контролироваться на сервере — храните их на бэкенде.

Основная терминология

  • Объектное хранилище (object store): контейнер для объектов, аналог таблицы в реляционных БД.
  • Ключ (key) и путь ключа (keyPath): уникальный идентификатор записи в хранилище.
  • Транзакция: атомарная группа операций чтения/записи.
  • Индекс: вспомогательная структура для поиска по полям объекта.

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

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

Пример создания запроса на открытие:

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

Обработка событий:

  • onsuccess происходит, когда база успешно открыта; доступ к DB через event.target.result.
  • onerror срабатывает при ошибках открытия.
  • onupgradeneeded срабатывает при первом создании базы или повышении версии — место для создания объектных хранилищ и индексов.

Пример обработчика success:

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

Пример обработчика onupgradeneeded и создания объектного хранилища:

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

  // Create an object store
  const userObjectStore = db.createObjectStore("userStore", {
    keyPath: "id",
    autoIncrement: true,
  });

  // Create indexes
  userObjectStore.createIndex("name", "name", { unique: false });
  userObjectStore.createIndex("email", "email", { unique: true });
};

Советы:

  • Всегда планируйте схему и версии заранее — частые обновления версии приводят к миграциям.
  • Обработчик onupgradeneeded выполняется в контексте транзакции на схему, поэтому в нём безопасно менять структуру хранилищ.

Создание объектного хранилища и индексов

Объектное хранилище — эквивалент таблицы, где хранятся объекты JavaScript. При создании указывайте keyPath или autoIncrement:

  • keyPath: имя поля, которое будет ключом.
  • autoIncrement: true: браузер сам будет генерировать числовые ключи.

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

Пример создания индексов:

userObjectStore.createIndex("name", "name", { unique: false });
userObjectStore.createIndex("email", "email", { unique: true });

Замечание: индексы нужно планировать с учётом пользовательских сценариев поиска и фильтрации.

Транзакции и режимы доступа

Транзакция группирует операции и обеспечивает их атомарность. Создаётся вызовом db.transaction(storeNames, mode), где modereadonly или readwrite.

Пример создания транзакции и добавления данных:

const addUserData = (userData, db) => {
  // Open a transaction
  const transaction = db.transaction("userStore", "readwrite");

  // Add data to the object store
  const userObjectStore = transaction.objectStore("userStore");

  // Make a request to add userData
  const request = userObjectStore.add(userData);

  // Handle a success event
  request.onsuccess = function (event) {
    //...
  };

  // Handle an error
  request.onerror = function (event) {
    //...
  };
};

Важно:

  • Транзакция автоматически закрывается после завершения всех запросов; не полагайтесь на долгоживущие транзакции.
  • Ошибка в одной операции может откатить все изменения в транзакции.

CRUD: добавление, чтение, обновление и удаление

Добавление (add): objectStore.add(value) — создаёт новую запись и выдаёт ошибку, если ключ уже существует.

Чтение: get(key) возвращает один объект по ключу; getAll() возвращает все записи или совпадающие по заданному диапазону.

Пример чтения по ключу:

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

  // Make a request to get the data
  const request = userObjectStore.get(id);

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

  request.onerror = function (event) {
    // Handle error
  };
};

Обновление: получите объект через get(), измените поля и сохраните через put()put заменяет существующую запись или создаёт новую с указанным ключом.

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

  // Make a request to get the data
  const getRequest = userObjectStore.get(id);

  // Handle a success event
  getRequest.onsuccess = function (event) {
    // Get the old user data
    const user = event.target.result;

    // Update the user data
    user.name = userData.name;
    user.email = userData.email;

    // Make a request to update the data
    const putRequest = userObjectStore.put(user);

    putRequest.onsuccess = function (event) {
      // Handle success
    };

    putRequest.onerror = function (event) {
      // Handle error
    };
  };

  getRequest.onerror = function (event) {
    // Handle error
  };
};

Удаление: objectStore.delete(key) удаляет запись по ключу.

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

  // Make a request to delete the data
  const request = userObjectStore.delete(id);

  request.onsuccess = function (event) {
    // Handle success
  };

  request.onerror = function (event) {
    // Handle error
  };
};

Шаблоны и лучшие практики

  • Всегда проверяйте, что db открыт и доступен перед операциями.
  • Используйте readwrite только когда нужно — это уменьшит конкуренцию и ускорит параллельные чтения.
  • Обрабатывайте onerror и onblocked события при обновлении версии базы (blocked означает, что другая вкладка всё ещё держит старую версию).
  • Вставляйте большие Blob и бинарные данные осторожно; для крупных файлов лучше использовать файловые API или серверное хранение.
  • Используйте индексы для часто используемых полей поиска и сортировки.

Производительность и ограничения

  • IndexedDB оптимизирован для больших объёмов данных и может хранить значительно больше, чем localStorage, но точный лимит зависит от браузера и настроек пользователя.
  • Пакетируйте операции в транзакции по возможностям, чтобы уменьшить количество открытий/закрытий транзакций.
  • Для массовой загрузки данных используйте стратегию порций (batching) и энергосберегающие паузы, чтобы не блокировать UI.

Миграция с localStorage на IndexedDB

Когда переходить:

  • Когда объём данных растёт и требуется индексированный поиск.
  • Когда нужны транзакции и гарантии целостности.

Мини-методика миграции:

  1. Создайте структуру в IndexedDB и занесите схему в onupgradeneeded.
  2. При старте приложения проверяйте наличие данных в localStorage.
  3. Вставляйте данные из localStorage в IndexedDB пакетами и отмечайте флаг миграции.
  4. После успешной миграции очищайте localStorage или оставляйте как резервную копию по версии.

Тестовые критерии миграции:

  • Все ключи и значения перенесены.
  • Индексы доступны и корректны.
  • Производительность не упала критически.

Безопасность и конфиденциальность

  • Данные в IndexedDB доступны только из того же происхождения (origin). Кросс-доменные запросы не допускаются.
  • Не храните критичные секреты (пароли в открытом виде, токены с длительным сроком) без шифрования. При необходимости шифруйте данные на клиенте перед сохранением.
  • Учитывайте требования конфиденциальности и локальные законы (например, GDPR) при долговременном хранении персональных данных: давайте возможность удаления и экспорта данных пользователю.
  • Всегда валидируйте данные перед вставкой, особенно если они приходят из внешних источников.

Отладка и распространённые ошибки

  • “QuotaExceededError”: Обычно возникает, если пользователь исчерпал выделенное место или браузер ограничил запись. Решения: сжатие данных, удаление неактуальных данных, запрос у пользователя действия.
  • “TransactionInactiveError”: Возникает, если попытаться использовать транзакцию после её завершения. Всегда выполняйте операции в пределах времени жизни транзакции.
  • onblocked при обновлении версии: означет, что другая вкладка не закрыла соединение. Покажите пользователю инструкцию закрыть другие вкладки.

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

Примеры тест-кейсов:

  • Создание базы и объектного хранилища: при открытии базы создаются указанные сторы и индексы.
  • CRUD базовый сценарий: добавить → получить → обновить → удалить, ожидаемый результат на каждом шаге.
  • Транзакционная целостность: при намеренно вызванной ошибке внутри транзакции все изменения откатываются.
  • Миграция: данные из localStorage корректно перенесены, флаг миграции установлен.

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

  • Все основные сценарии CRUD проходят без ошибок.
  • Индексы возвращают ожидаемые результаты при поиске.
  • Приложение обрабатывает ошибки квоты и блокировки без падения.

Роли и контрольный список внедрения

Для разработчика:

  • Спроектировать схему и версионирование базы.
  • Реализовать обработчики onsuccess, onerror, onupgradeneeded.
  • Добавить логику миграции и бэкап/очистку localStorage.

Для QA:

  • Проверить сценарии CRUD и транзакции.
  • Тестировать обновления версии и блокировки между вкладками.
  • Протестировать поведение при ограниченной квоте диска.

Для продакт-менеджера:

  • Оценить необходимость IndexedDB vs localStorage.
  • Утвердить правила удерживания данных и сроки хранения.

Альтернативы и когда выбирать их

  • localStorage: простые пары ключ–значение, синхронный API, ограничен объёмом и функциональностью.
  • Cache API + Service Workers: лучше для кеширования HTTP-ресурсов и офлайн-страниц.
  • WebSQL (устаревший): не поддерживается во многих браузерах.
  • Серверное хранение: для централизованного доступа и бэкапа данных.

Выбор зависит от сценария: для сложных локальных структур и офлайна — IndexedDB; для простых настроек — localStorage; для кеша сетевых ответов — Cache API.

Совместимость и миграционные заметки

IndexedDB поддерживается в большинстве современных браузеров, но поведение квот и внутренних оптимизаций может отличаться. При обновлении версии базы:

  • Обрабатывайте onblocked, чтобы информировать пользователя о необходимости закрыть другие вкладки.
  • Выполняйте изменения схемы последовательно: миграция должна быть обратимо-предсказуемой.

Заключение

IndexedDB — мощный инструмент для клиентского хранения крупных и структурированных данных. Он обеспечивает транзакционность, индексацию и офлайн-доступ. Тем не менее, его следует использовать там, где это оправдано архитектурно: когда данные превышают возможности localStorage или требуется сложный поиск и согласованность.

Важно:

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

Короткая шпаргалка для старта:

  1. Откройте базу: indexedDB.open(name, version).
  2. В onupgradeneeded создайте object stores и индексы.
  3. В onsuccess получите db и выполняйте транзакции через db.transaction.
  4. Для записи используйте add()/put(), для чтения — get()/getAll(), для удаления — delete().

Важно: если приложение использует личные данные, добавьте на страницу настройки для экспорта и удаления данных по требованию пользователя.

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

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

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство