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

В этой статье я расскажу, как работает IndexedDB, какие архитектурные решения стоит принять и какие ошибки чаще всего встречаются при реализации. Материал ориентирован на практические задачи — от простого CRUD до синхронизации и отладки.

Почему IndexedDB — это не просто ещё один способ хранить данные

В отличие от cookie или localStorage, IndexedDB асинхронна и заточена под хранение больших объёмов данных. Она позволяет сохранять объекты в исходной структуре благодаря алгоритму structured clone, что особенно удобно для приложений с множеством сложных состояний.

IndexedDB подходит для офлайн-приложений, кэширования ответов API, хранения медиафайлов и работы с большими наборами записей. Если вам нужен быстрый доступ к данным без постоянного обращения к серверу, это один из лучших инструментов в браузере.

Ключевые понятия и устройство базы

База IndexedDB состоит из баз данных, в каждой из которых есть object store. Object store — аналог таблицы, но без строгой схемы. Для быстрого поиска используются индексы, которые вы создаёте вручную при изменении версии базы.

Операции в IndexedDB оборачиваются запросами и транзакциями. Транзакция задаёт набор object store, режим доступа и гарантирует атомарность операций внутри неё. Версия базы определяет миграции и структуру, а событие onupgradeneeded служит для создания или изменения хранилищ и индексов.

Основные операции: что и как делать

Частые операции — get, put, add, delete, clear. put вставляет или обновляет запись, add требует уникальности ключа. Для перебора записей используют openCursor, а для массового получения существуют getAll и getAllKeys.

Транзакции бывают readonly и readwrite. Они живут до тех пор, пока не завершатся все запросы внутри. Ошибка в одном запросе откатывает транзакцию, что удобно для сохранения целостности данных.

Пошаговое использование: от открытия к записи

Сначала открывают подключение к базе методом indexedDB.open с указанием имени и, при необходимости, версии. В обработчике onupgradeneeded создают object store и индексы. После успешного открытия можно начинать транзакции и выполнять запросы.

Ниже небольшой пример базового шаблона для добавления записи. Код даёт представление о последовательности событий и о том, где стоит размещать логику создания схемы.

const request = indexedDB.open('notes-db', 1);

request.onupgradeneeded = event => {
  const db = event.target.result;
  if (!db.objectStoreNames.contains('notes')) {
    const store = db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true });
    store.createIndex('by-date', 'updatedAt');
  }
};

request.onsuccess = event => {
  const db = event.target.result;
  const tx = db.transaction('notes', 'readwrite');
  const store = tx.objectStore('notes');
  store.put({ title: 'Дело', content: 'Сделать уборку', updatedAt: Date.now() });
  tx.oncomplete = () => db.close();
};

Реальный кейс: офлайн блокнот и синхронизация

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

Главная проблема, с которой столкнулся — управление версиями. При добавлении новых полей потребовалось аккуратно мигрировать старые записи, не потеряв данные. Решение простое: в onupgradeneeded писать понятные миграции и тестировать их на реальных бэкапах.

Стратегии синхронизации и кеширования

Для синхронизации полезно хранить метаданные: локальную временную метку, статус синхронизации и хеш содержимого. Это позволяет минимизировать пересылку данных и корректно разрешать конфликты при параллельных правках.

Если приложение использует service worker, IndexedDB часто применяется в связке с Cache API: один отвечает за ответы сервера, другой за структуру данных и черновики. Такой подход даёт гибкость и экономит сетевой трафик.

Сравнение с другими хранилищами

Ниже таблица с упрощённым сравнением IndexedDB, localStorage и Cache API по основным характеристикам. Это поможет выбрать инструмент в зависимости от задачи.

Характеристика localStorage IndexedDB Cache API
Объём Малый, синхронный Большие объёмы, асинхронно Оптимизирован для ответов сети
Тип данных Строки Сложные объекты, Blobs Ответы Request/Response
Использование Небольшие настройки Базы приложений, офлайн Кеширование ресурсов

Ограничения, безопасность и особенности

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

Structured clone не сохраняет функции и некоторые специфические объекты. Также в браузере нет полноценного SQL-подобного языка запросов, поэтому сложные выборки реализуются через индексы и курсоры. При работе с бинарными файлами учитывайте влияние на квоту и производительность.

Ошибки, которые чаще всего встречаются

Первая ошибка — попытка открыть транзакцию после закрытия соединения или вне обработчика onsuccess. Вторая — неправильная миграция схемы, когда onupgradeneeded не учитывает старые версии и теряет данные. Третья — надежда на синхронную работу; многие забывают, что запросы асинхронны, и пытаются читать результат до завершения операции.

Лучше сразу обернуть API в промисы или использовать библиотеку-обёртку. Это уменьшает вероятность гонок и упрощает обработку ошибок.

Инструменты отладки и тестирования

Chrome DevTools показывает базы IndexedDB на вкладке Application. Там можно просматривать object store и индексы, удалять записи и экспортировать данные. Firefox и другие браузеры предоставляют похожие инструменты, но детали интерфейса отличаются.

Для тестирования в CI имеет смысл использовать mock-реализации, например fake-indexeddb. Это позволяет запускать юнит-тесты без реального браузера и контролировать состояние базы.

Библиотеки и полезные обёртки

Если не хочется писать множество промисов вручную, стоит обратить внимание на библиотеку idb. Она упрощает работу с транзакциями и делает код чище. Но иногда собственная обёртка под нужды приложения даёт лучший контроль и более тонкие оптимизации.

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

Рекомендации и практические приёмы

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

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

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

IndexedDB — мощный инструмент, но он требует аккуратности. Простые интерфейсы и продуманная схема облегчают дальнейшую поддержку и масштабирование приложения.

Когда всё настроено правильно, вы получите надёжный способ хранения больших объёмов и гибкую платформу для офлайн-режима, кэширования и сложных клиентских операций. Это не магия, а грамотная архитектоника клиентской части приложения.