IndexedDB
IndexedDB is a transactional, asynchronous database built into the browser that stores large amounts of structured data — objects, arrays, even files — keyed for fast lookup and queryable through indexes.
Learn IndexedDB in our free JavaScript course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
It is what powers offline-capable web apps: caching API data, saving drafts, storing user-generated content, and keeping everything available when the network is not.
💡 About the runnable demos: The real indexedDB global only exists in a browser, so it lives in the read-only code blocks. Each Try it Yourself runs a tiny promise-based key-value store backed by a Map that mirrors the same async put / get / getAll shape, so you see the real data flow in your console.
🏦 Real-World Analogy: localStorage is a sticky note on your monitor — quick, tiny, and only good for short text. IndexedDB is a filing-cabinet bank vault : drawers (object stores) of folders keyed by account number, indexes so you can also find a folder by name, and a teller who insists every set of changes happens as one atomic transaction — all or nothing.
You open a database by name and version with indexedDB.open . The first time (or on a version bump) the onupgradeneeded event fires — the only place you may create object stores and indexes. Every operation returns an IDBRequest that later fires onsuccess or onerror ; you read the value from request.result .
That callback-and-event dance is why everyone wraps a request in a promise. Here is the wrapper pattern running against a Map-backed store that behaves the same way:
All reads and writes happen inside a transaction . Open one with db.transaction(storeName, mode) where mode is "readonly" or "readwrite" , grab the store with tx.objectStore(name) , then call put , get , or getAll . Everything in the transaction commits together or rolls back together.
⚠️ Transactions are short-lived: a transaction auto-commits as soon as it has no more pending requests. Do not await unrelated work in the middle — the transaction may close out from under you.
The runnable store below implements put , get , and getAll with the same async signatures, backed by a Map:
Replace the blank with await so the value resolves before you log it.
By default you can only fetch by primary key. An index lets you query by another property. You declare it in onupgradeneeded with store.createIndex(name, keyPath) , then read through it with store.index(name).getAll(value) . Indexes can also enforce uniqueness.
The model below adds a simple secondary index so you can query by tag, not just by id:
A write needs a read-write transaction. Replace the blank with the correct mode string.
Only inside the onupgradeneeded event handler.
No — it only stores strings, so you must JSON.stringify . IndexedDB stores structured objects directly.
From request.result inside the onsuccess handler.
Build a promise-based cache: set(key, value, ttlMs) stores a record with an expiry time, and get(key) returns the value only if it has not expired (otherwise undefined ). This is the heart of an offline data cache.
Up next: The Canvas API — draw graphics with JavaScript. 🎨
Practice quiz
How is IndexedDB different from localStorage?
- It only stores strings
- It blocks the main thread
- It is a transactional, asynchronous database that stores structured objects and large data
- It holds only a few kilobytes
Answer: It is a transactional, asynchronous database that stores structured objects and large data. IndexedDB is a real transactional, asynchronous database that stores structured objects (and files) and far more data than localStorage.
What is an object store in IndexedDB?
- IndexedDB's equivalent of a table that holds records keyed by a key
- A CSS file
- A network socket
- A type of index only
Answer: IndexedDB's equivalent of a table that holds records keyed by a key. An object store is like a table — it holds records keyed by a key.
Where are you allowed to create object stores and indexes?
- Anywhere after opening the database
- Inside onsuccess
- Inside a readonly transaction
- Only inside the onupgradeneeded event handler
Answer: Only inside the onupgradeneeded event handler. Object stores and indexes can only be created in the onupgradeneeded event, which fires on first open or version bump.
How do reads and writes happen in IndexedDB?
- Directly on the store with no setup
- Inside a transaction opened with db.transaction(store, mode)
- Through localStorage
- Only synchronously
Answer: Inside a transaction opened with db.transaction(store, mode). All reads and writes happen inside a transaction opened with db.transaction(storeName, mode).
Which transaction mode is required to write data?
- 'readwrite'
- 'readonly'
- 'write-only'
- 'upgrade'
Answer: 'readwrite'. Writing requires the 'readwrite' mode; 'readonly' blocks writes.
Why does IndexedDB use IDBRequest with onsuccess/onerror instead of returning values?
- It is synchronous
- To save memory
- It predates promises, so each operation fires events later and you read request.result
- Because it uses callbacks for styling
Answer: It predates promises, so each operation fires events later and you read request.result. It is an older event-based API; each operation returns an IDBRequest that fires onsuccess/onerror, with the value on request.result.
What is the common pattern for using IndexedDB with async/await?
- Avoid transactions
- Wrap each IDBRequest in a Promise that resolves on success and rejects on error
- Use localStorage instead
- Poll request.result in a loop
Answer: Wrap each IDBRequest in a Promise that resolves on success and rejects on error. Because the request/event model is awkward, you wrap each request in a Promise to use async/await.
What is an index used for in IndexedDB?
- To store images
- To speed up the network
- To encrypt the database
- To query records by a property other than the primary key
Answer: To query records by a property other than the primary key. By default you query by primary key; an index lets you query by another property and can enforce uniqueness.
Why must you avoid awaiting unrelated work in the middle of a transaction?
- It uses too much memory
- The transaction auto-commits when it has no pending requests and may close out from under you
- It encrypts the data
- It deletes the object store
Answer: The transaction auto-commits when it has no pending requests and may close out from under you. Transactions are short-lived and auto-commit when idle, so awaiting unrelated work can close the transaction.
Can localStorage store a JavaScript object directly?
- Yes, any object
- Only arrays
- No — it only stores strings, so you must JSON.stringify
- Only numbers
Answer: No — it only stores strings, so you must JSON.stringify. localStorage only stores strings (requiring JSON.stringify), while IndexedDB stores structured objects directly.