Lesson 0002 · ~12 minutes
One skill: explain how a connection opens, when schema can change, and how primary keys are chosen — the design surface behind “we’ll put it in IndexedDB.”
You can sketch open → upgradeneeded → success, name
in-line vs out-of-line keys, and flag multi-tab upgrade risk in a
design review — still without deep CRUD.
Per MDN’s basic terminology (and the IndexedDB spec):
indexedDB.open(name, version). Many connections can
exist at once (tabs, workers).
MDN’s hard constraints that matter for system design:
everything is transactional, the API is
mostly asynchronous (requests + events, or a promise
wrapper like idb), there is no SQL, and
there is no built-in server sync.
Opening is a request. Three events design reviews care about:
Requested version is higher than the existing DB (or DB is new).
Only here may you create/delete object stores and
indexes. Spec calls this a versionchange upgrade
transaction.
Connection is open at the requested version. Do normal read/write transactions here — not structural schema edits.
Another tab/worker still holds an old connection and has not closed
after versionchange. Upgrade waits. Product UX problem.
const request = indexedDB.open("notes-db", 1);
request.onupgradeneeded = (event) => {
const db = request.result;
// event.oldVersion === 0 on first create
if (!db.objectStoreNames.contains("notes")) {
db.createObjectStore("notes", { keyPath: "id" });
}
};
request.onsuccess = () => {
const db = request.result;
// ready for db.transaction("notes", "readonly") …
};
request.onblocked = () => {
// tell user to close other tabs of this origin
};
Treat version bumps as deploys. Migration steps must
run from every historical oldVersion your users still
have. Concurrent tabs must close or reload on
versionchange, or new clients sit in
blocked. If you cannot afford that ceremony, keep the
schema tiny and stable.
Every record has a unique key in its store. MDN/spec distinguish:
| Shape | How | When it fits |
|---|---|---|
| In-line key |
createObjectStore("x", { keyPath: "id" }) — key is
a property on the value
|
Domain already has a stable id (UUID, email, ISBN) |
| Out-of-line key |
No keyPath; you pass the key to add/put
|
Key is external or not part of the stored object shape |
| Key generator |
{ autoIncrement: true } (optionally with keyPath)
|
Local-only sequence is fine; no natural business key |
Keys themselves are not free-form objects: valid key types include numbers, strings, dates, binary, and arrays of keys (see MDN/spec). Bad keys fail the request — design stable key types early.
Indexes (secondary lookup paths) are created on a store during upgrade only. We’ll use them next; today know they are part of schema cost, not free “add a WHERE later.”
The
spec’s versionchange guidance
is explicit: one database version at a time; upgrades need exclusive
access. Open connections receive versionchange so they can
close; the upgrader may see blocked if they don’t.
For design reviews: “we’ll just bump the version in the next release” implies a plan for long-lived tabs, shared workers, and users who never fully close the SPA.
You still have not practiced get/put
transaction lifetime quirks. Schema + open first — so CRUD sits on a
correct mental model.
Equal-length choices. Immediate feedback.
When is it legal to call createObjectStore for a new
“attachments” bucket?
Contacts always have a unique email field you trust as
identity. Best primary-key style?
Release ships DB version 4. Some users leave yesterday’s tab open for days. What must the old tab do?
What does indexedDB.open("app", 3) primarily express?
Which limitation should you state in a design review about IndexedDB’s job?
upgradeneeded; multi-tab must
cooperate or you hit blocked.
MDN — IndexedDB key characteristics and basic terminology (this is the article you skipped after lesson 1 — now it is the main homework). Focus on key characteristics + core terms (database, connection, object store, transaction, key, index). Optional: skim the open/upgrade examples in the W3C IndexedDB introduction.