๐Ÿ“ฆ leonardomso / 33-js-concepts

๐Ÿ“„ indexeddb.mdx ยท 1028 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028---
title: "IndexedDB: Client-Side Database Storage in JavaScript"
sidebarTitle: "IndexedDB: Client-Side Database Storage"
description: "Learn IndexedDB for client-side storage in JavaScript. Store structured data, create indexes, perform transactions, and build offline-capable apps."
---

What happens when localStorage's 5MB limit isn't enough? How do you store thousands of records, search them efficiently, or keep an app working offline with real data?

Meet **IndexedDB** โ€” a full database built into every modern browser. Unlike localStorage's simple key-value pairs, IndexedDB lets you store massive amounts of structured data, create indexes for fast lookups, and run transactions that keep your data consistent.

```javascript
// Store and retrieve complex data with IndexedDB
const request = indexedDB.open('MyApp', 1)

request.onupgradeneeded = (event) => {
  const db = event.target.result
  const store = db.createObjectStore('users', { keyPath: 'id' })
  store.createIndex('email', 'email', { unique: true })
}

request.onsuccess = (event) => {
  const db = event.target.result
  const tx = db.transaction('users', 'readwrite')
  tx.objectStore('users').add({ id: 1, name: 'Alice', email: 'alice@example.com' })
}
```

IndexedDB is the backbone of offline-first applications, Progressive Web Apps (PWAs), and any app that needs to work without a network connection. It's more complex than localStorage, but far more powerful.

<Info>
**What you'll learn in this guide:**
- What IndexedDB is and when to use it instead of localStorage
- How to open databases and handle versioning
- Creating object stores and indexes for your data
- Performing CRUD operations within transactions
- Iterating over data with cursors
- Using Promise wrappers for cleaner async code
- Real-world patterns for offline-capable applications
</Info>

<Warning>
**Prerequisite:** IndexedDB is heavily asynchronous. This guide assumes you're comfortable with [Promises](/concepts/promises) and [async/await](/concepts/async-await). If those concepts are fuzzy, read those guides first!
</Warning>

---

## What is IndexedDB?

**IndexedDB** is a low-level browser API for storing large amounts of structured data on the client side. It's a transactional, NoSQL database that uses object stores (similar to tables) to organize data, supports indexes for efficient queries, and can store almost any JavaScript value including objects, arrays, files, and blobs.

Think of IndexedDB as a real database that lives in the browser. While [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) gives you a simple string-only key-value store with ~5MB limit, IndexedDB can store gigabytes of structured data with proper querying capabilities.

```
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    BROWSER STORAGE COMPARISON                            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                          โ”‚
โ”‚   localStorage                      IndexedDB                            โ”‚
โ”‚   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€                     โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€                            โ”‚
โ”‚                                                                          โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”               โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚   โ”‚ key: "user"     โ”‚               โ”‚  Database: "MyApp"              โ”‚  โ”‚
โ”‚   โ”‚ value: "{...}"  โ”‚               โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚  โ”‚
โ”‚   โ”‚                 โ”‚               โ”‚  โ”‚ Object Store: "users"     โ”‚  โ”‚  โ”‚
โ”‚   โ”‚ key: "theme"    โ”‚               โ”‚  โ”‚  โ”œโ”€ id: 1, name: "Alice"  โ”‚  โ”‚  โ”‚
โ”‚   โ”‚ value: "dark"   โ”‚               โ”‚  โ”‚  โ”œโ”€ id: 2, name: "Bob"    โ”‚  โ”‚  โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜               โ”‚  โ”‚  โ””โ”€ (thousands more...)   โ”‚  โ”‚  โ”‚
โ”‚                                     โ”‚  โ”‚                           โ”‚  โ”‚  โ”‚
โ”‚   โ€ข ~5MB limit                      โ”‚  โ”‚  Indexes: email, role     โ”‚  โ”‚  โ”‚
โ”‚   โ€ข Strings only                    โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚  โ”‚
โ”‚   โ€ข Synchronous                     โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚  โ”‚
โ”‚   โ€ข No querying                     โ”‚  โ”‚ Object Store: "posts"     โ”‚  โ”‚  โ”‚
โ”‚                                     โ”‚  โ”‚  โ”œโ”€ (structured data)     โ”‚  โ”‚  โ”‚
โ”‚                                     โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚  โ”‚
โ”‚                                     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                                                          โ”‚
โ”‚                                     โ€ข Gigabytes of storage               โ”‚
โ”‚                                     โ€ข Any JS value (objects, blobs)      โ”‚
โ”‚                                     โ€ข Asynchronous                       โ”‚
โ”‚                                     โ€ข Indexed queries                    โ”‚
โ”‚                                                                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
```

<CardGroup cols={2}>
  <Card title="IndexedDB API โ€” MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">
    The official MDN landing page covering all IndexedDB interfaces including IDBDatabase, IDBTransaction, and IDBObjectStore
  </Card>
  <Card title="Storage Quotas โ€” MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria">
    How browsers allocate storage space and when data gets evicted
  </Card>
</CardGroup>

---

## The Filing Cabinet Analogy

Imagine your browser has a filing cabinet for each website you visit.

**localStorage** is like a single drawer with sticky notes โ€” quick and simple, but limited. You can only store short text messages, and there's not much room.

**IndexedDB** is like having an entire filing cabinet system with multiple drawers (object stores), folders within each drawer (indexes), and the ability to store complete documents, photos, or any type of file.

```
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    THE FILING CABINET ANALOGY                            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                          โ”‚
โ”‚   DATABASE = Filing Cabinet                                              โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚   โ”‚                                                                  โ”‚   โ”‚
โ”‚   โ”‚   OBJECT STORE = Drawer         OBJECT STORE = Drawer            โ”‚   โ”‚
โ”‚   โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”          โ”‚   โ”‚
โ”‚   โ”‚   โ”‚   "users"           โ”‚       โ”‚   "products"        โ”‚          โ”‚   โ”‚
โ”‚   โ”‚   โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚       โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚          โ”‚   โ”‚
โ”‚   โ”‚   โ”‚   โ”‚ Record        โ”‚ โ”‚       โ”‚   โ”‚ Record        โ”‚ โ”‚          โ”‚   โ”‚
โ”‚   โ”‚   โ”‚   โ”‚ id: 1         โ”‚ โ”‚       โ”‚   โ”‚ sku: "A001"   โ”‚ โ”‚          โ”‚   โ”‚
โ”‚   โ”‚   โ”‚   โ”‚ name: "Alice" โ”‚ โ”‚       โ”‚   โ”‚ name: "Widget"โ”‚ โ”‚          โ”‚   โ”‚
โ”‚   โ”‚   โ”‚   โ”‚ email: "..."  โ”‚ โ”‚       โ”‚   โ”‚ price: 29.99  โ”‚ โ”‚          โ”‚   โ”‚
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚       โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚          โ”‚   โ”‚
โ”‚   โ”‚   โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚       โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚          โ”‚   โ”‚
โ”‚   โ”‚   โ”‚   โ”‚ Record        โ”‚ โ”‚       โ”‚   โ”‚ Record        โ”‚ โ”‚          โ”‚   โ”‚
โ”‚   โ”‚   โ”‚   โ”‚ id: 2         โ”‚ โ”‚       โ”‚   โ”‚ sku: "B002"   โ”‚ โ”‚          โ”‚   โ”‚
โ”‚   โ”‚   โ”‚   โ”‚ name: "Bob"   โ”‚ โ”‚       โ”‚   โ”‚ ...           โ”‚ โ”‚          โ”‚   โ”‚
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚       โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚          โ”‚   โ”‚
โ”‚   โ”‚   โ”‚                     โ”‚       โ”‚                     โ”‚          โ”‚   โ”‚
โ”‚   โ”‚   โ”‚   INDEX: "email"    โ”‚       โ”‚   INDEX: "price"    โ”‚          โ”‚   โ”‚
โ”‚   โ”‚   โ”‚   (sorted labels)   โ”‚       โ”‚   (sorted labels)   โ”‚          โ”‚   โ”‚
โ”‚   โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜          โ”‚   โ”‚
โ”‚   โ”‚                                                                  โ”‚   โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                                                                          โ”‚
โ”‚   KEY = The label on each folder (how you find records)                  โ”‚
โ”‚   INDEX = Alphabetical tabs that let you find folders by other fields   โ”‚
โ”‚   TRANSACTION = Checking out folders (ensures nobody else modifies them) โ”‚
โ”‚                                                                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
```

Just like a real filing system:
- You open the **cabinet** (database) before accessing anything
- You pull out a **drawer** (object store) to work with specific types of records
- You use **labels** (keys) to identify individual folders
- You use **alphabetical tabs** (indexes) to find folders by different criteria
- You **check out** folders (transactions) so no one else modifies them while you're working

---

## Opening a Database

Before you can store or retrieve data, you need to open a connection to a database. If the database doesn't exist, IndexedDB creates it for you.

```javascript
// Open (or create) a database named "MyApp" at version 1
const request = indexedDB.open('MyApp', 1)

// This fires if the database needs to be created or upgraded
request.onupgradeneeded = (event) => {
  const db = event.target.result
  console.log('Database created or upgraded!')
}

// This fires when the database is ready to use
request.onsuccess = (event) => {
  const db = event.target.result
  console.log('Database opened successfully!')
}

// This fires if something goes wrong
request.onerror = (event) => {
  console.error('Error opening database:', event.target.error)
}
```

Notice that IndexedDB uses an **event-based pattern** rather than Promises. The [`indexedDB.open()`](https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory/open) method returns a request object, and you attach event handlers to it.

### Database Versioning

The second argument to `open()` is the **version number**. This is how IndexedDB handles schema migrations:

```javascript
// First time: create the database at version 1
const request = indexedDB.open('MyApp', 1)

request.onupgradeneeded = (event) => {
  const db = event.target.result
  
  // Create object stores only in onupgradeneeded
  if (!db.objectStoreNames.contains('users')) {
    db.createObjectStore('users', { keyPath: 'id' })
  }
}
```

When you need to change the schema (add a new store, add an index), you increment the version:

```javascript
// Later: upgrade to version 2
const request = indexedDB.open('MyApp', 2)

request.onupgradeneeded = (event) => {
  const db = event.target.result
  const oldVersion = event.oldVersion
  
  // Run migrations based on the old version
  if (oldVersion < 1) {
    db.createObjectStore('users', { keyPath: 'id' })
  }
  if (oldVersion < 2) {
    db.createObjectStore('posts', { keyPath: 'id' })
  }
}
```

<Warning>
**The Version Rule:** You can only create or modify object stores inside the `onupgradeneeded` event. Trying to create a store elsewhere throws an error. Always increment the version number when you need to change the database structure.
</Warning>

---

## Object Stores and Keys

An **object store** is like a table in a traditional database. It holds a collection of records, and each record must have a unique key.

### Creating Object Stores

You create object stores inside `onupgradeneeded`:

```javascript
request.onupgradeneeded = (event) => {
  const db = event.target.result
  
  // Option 1: Use a property from the object as the key (keyPath)
  const usersStore = db.createObjectStore('users', { keyPath: 'id' })
  // Records must have an 'id' property: { id: 1, name: 'Alice' }
  
  // Option 2: Auto-generate keys
  const logsStore = db.createObjectStore('logs', { autoIncrement: true })
  // Keys are generated automatically: 1, 2, 3, ...
  
  // Option 3: Both - auto-increment and store the key in the object
  const postsStore = db.createObjectStore('posts', { 
    keyPath: 'id', 
    autoIncrement: true 
  })
  // Key is auto-generated AND stored in the 'id' property
}
```

### Creating Indexes

**Indexes** let you query records by fields other than the primary key:

```javascript
request.onupgradeneeded = (event) => {
  const db = event.target.result
  const store = db.createObjectStore('users', { keyPath: 'id' })
  
  // Create an index on the 'email' field (must be unique)
  store.createIndex('email', 'email', { unique: true })
  
  // Create an index on 'role' (not unique - many users can share a role)
  store.createIndex('role', 'role', { unique: false })
}
```

Later, you can query by these indexes:

```javascript
// Find a user by email (instead of by id)
const index = store.index('email')
const request = index.get('alice@example.com')
```

---

## CRUD Operations

All data operations in IndexedDB happen inside **transactions**. A transaction ensures that a group of operations either all succeed or all fail together.

### Creating (Add)

```javascript
function addUser(db, user) {
  // 1. Start a transaction in 'readwrite' mode
  const tx = db.transaction('users', 'readwrite')
  
  // 2. Get the object store
  const store = tx.objectStore('users')
  
  // 3. Add the data
  const request = store.add(user)
  
  request.onsuccess = () => {
    console.log('User added with id:', request.result)
  }
  
  request.onerror = () => {
    console.error('Error adding user:', request.error)
  }
}

// Usage
addUser(db, { id: 1, name: 'Alice', email: 'alice@example.com' })
```

<Tip>
**add() vs put():** Use `add()` when inserting new records. It fails if a record with the same key already exists. Use `put()` when you want to insert OR update. It overwrites existing records.
</Tip>

### Reading (Get)

```javascript
function getUser(db, id) {
  const tx = db.transaction('users', 'readonly')
  const store = tx.objectStore('users')
  const request = store.get(id)
  
  request.onsuccess = () => {
    if (request.result) {
      console.log('Found user:', request.result)
    } else {
      console.log('User not found')
    }
  }
}

// Get all records
function getAllUsers(db) {
  const tx = db.transaction('users', 'readonly')
  const store = tx.objectStore('users')
  const request = store.getAll()
  
  request.onsuccess = () => {
    console.log('All users:', request.result)  // Array of all user objects
  }
}
```

### Updating (Put)

```javascript
function updateUser(db, user) {
  const tx = db.transaction('users', 'readwrite')
  const store = tx.objectStore('users')
  
  // put() updates if exists, inserts if not
  const request = store.put(user)
  
  request.onsuccess = () => {
    console.log('User updated')
  }
}

// Usage - update Alice's email
updateUser(db, { id: 1, name: 'Alice', email: 'alice.new@example.com' })
```

### Deleting

```javascript
function deleteUser(db, id) {
  const tx = db.transaction('users', 'readwrite')
  const store = tx.objectStore('users')
  const request = store.delete(id)
  
  request.onsuccess = () => {
    console.log('User deleted')
  }
}

// Delete all records
function clearAllUsers(db) {
  const tx = db.transaction('users', 'readwrite')
  const store = tx.objectStore('users')
  store.clear()
}
```

---

## Understanding Transactions

Transactions are a critical concept in IndexedDB. They ensure data integrity by grouping operations together.

```
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                       TRANSACTION LIFECYCLE                              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                          โ”‚
โ”‚   1. CREATE                2. EXECUTE                3. COMPLETE         โ”‚
โ”‚   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€                โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€                 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€           โ”‚
โ”‚                                                                          โ”‚
โ”‚   const tx = db           store.add(...)            tx.oncomplete        โ”‚
โ”‚     .transaction(         store.put(...)            All changes saved!   โ”‚
โ”‚       'users',            store.delete(...)                              โ”‚
โ”‚       'readwrite'         โ†“                         tx.onerror           โ”‚
โ”‚     )                     (all or nothing)          All changes rolled   โ”‚
โ”‚                                                     back!                โ”‚
โ”‚                                                                          โ”‚
โ”‚   Transaction Modes:                                                     โ”‚
โ”‚   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€                                                      โ”‚
โ”‚   'readonly'   - Only reading data (faster, can run in parallel)         โ”‚
โ”‚   'readwrite'  - Reading and writing (locks the store)                   โ”‚
โ”‚                                                                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
```

### Transaction Auto-Commit

Transactions automatically commit when there are no more pending requests:

```javascript
const tx = db.transaction('users', 'readwrite')
const store = tx.objectStore('users')

store.add({ id: 1, name: 'Alice' })
store.add({ id: 2, name: 'Bob' })

// Transaction auto-commits after both adds complete
tx.oncomplete = () => {
  console.log('Both users saved!')
}
```

### The Transaction Timing Trap

Here's a common mistake. Transactions auto-commit quickly, so you can't do async work in the middle:

```javascript
// โŒ WRONG - Transaction will close before fetch completes
const tx = db.transaction('users', 'readwrite')
const store = tx.objectStore('users')

const response = await fetch('/api/user')  // Network request
const user = await response.json()
store.add(user)  // ERROR: Transaction is no longer active!
```

```javascript
// โœ“ CORRECT - Fetch first, then use IndexedDB
const response = await fetch('/api/user')
const user = await response.json()

const tx = db.transaction('users', 'readwrite')
const store = tx.objectStore('users')
store.add(user)  // Works!
```

<Warning>
**The Auto-Commit Rule:** Transactions close automatically after the current JavaScript "tick" if there are no pending requests. Never put `await` calls to external APIs inside a transaction. Fetch your data first, then write to IndexedDB.
</Warning>

---

## Iterating with Cursors

When you need to process records one at a time (instead of loading everything into memory), use a **cursor**:

```javascript
function iterateUsers(db) {
  const tx = db.transaction('users', 'readonly')
  const store = tx.objectStore('users')
  const request = store.openCursor()
  
  request.onsuccess = (event) => {
    const cursor = event.target.result
    
    if (cursor) {
      // Process the current record
      console.log('Key:', cursor.key, 'Value:', cursor.value)
      
      // Move to the next record
      cursor.continue()
    } else {
      // No more records
      console.log('Done iterating')
    }
  }
}
```

### Cursor with Key Ranges

You can limit which records the cursor visits using [`IDBKeyRange`](https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange):

```javascript
function getUsersInRange(db, minId, maxId) {
  const tx = db.transaction('users', 'readonly')
  const store = tx.objectStore('users')
  
  // Only iterate over keys between minId and maxId
  const range = IDBKeyRange.bound(minId, maxId)
  const request = store.openCursor(range)
  
  request.onsuccess = (event) => {
    const cursor = event.target.result
    if (cursor) {
      console.log(cursor.value)
      cursor.continue()
    }
  }
}

// Other key range options:
IDBKeyRange.only(5)           // Only key === 5
IDBKeyRange.lowerBound(5)     // key >= 5
IDBKeyRange.upperBound(10)    // key <= 10
IDBKeyRange.bound(5, 10)      // 5 <= key <= 10
IDBKeyRange.bound(5, 10, true, false)  // 5 < key <= 10
```

---

## Using Promise Wrappers

The callback-based API can get messy. Most developers use a Promise wrapper library. The most popular is **idb** by Jake Archibald:

```javascript
// Using the idb library (https://github.com/jakearchibald/idb)
import { openDB } from 'idb'

async function demo() {
  // Open database with Promises
  const db = await openDB('MyApp', 1, {
    upgrade(db) {
      db.createObjectStore('users', { keyPath: 'id' })
    }
  })
  
  // Add a user
  await db.add('users', { id: 1, name: 'Alice' })
  
  // Get a user
  const user = await db.get('users', 1)
  console.log(user)  // { id: 1, name: 'Alice' }
  
  // Get all users
  const allUsers = await db.getAll('users')
  
  // Update
  await db.put('users', { id: 1, name: 'Alice Updated' })
  
  // Delete
  await db.delete('users', 1)
}
```

<Tip>
**The idb Advantage:** The idb library (~1.2kB) wraps IndexedDB's event-based API with Promises, making it work beautifully with async/await. It's the recommended way to use IndexedDB in modern applications.
</Tip>

### Building Your Own Wrapper

If you prefer not to add a dependency, here's a simple helper pattern:

```javascript
// Promisify an IDBRequest
function promisifyRequest(request) {
  return new Promise((resolve, reject) => {
    request.onsuccess = () => resolve(request.result)
    request.onerror = () => reject(request.error)
  })
}

// Promisify opening a database
function openDatabase(name, version, onUpgrade) {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(name, version)
    request.onupgradeneeded = (event) => onUpgrade(event.target.result)
    request.onsuccess = () => resolve(request.result)
    request.onerror = () => reject(request.error)
  })
}

// Usage
async function demo() {
  const db = await openDatabase('MyApp', 1, (db) => {
    db.createObjectStore('users', { keyPath: 'id' })
  })
  
  const tx = db.transaction('users', 'readwrite')
  const store = tx.objectStore('users')
  
  await promisifyRequest(store.add({ id: 1, name: 'Alice' }))
  const user = await promisifyRequest(store.get(1))
  console.log(user)
}
```

---

## IndexedDB vs Other Storage Options

When should you use IndexedDB instead of other browser storage options?

| Feature | localStorage | sessionStorage | IndexedDB | Cookies |
|---------|-------------|----------------|-----------|---------|
| **Storage Limit** | ~5MB | ~5MB | Gigabytes | ~4KB |
| **Data Types** | Strings only | Strings only | Any JS value | Strings only |
| **Async** | No (blocks UI) | No (blocks UI) | Yes | No |
| **Queryable** | No | No | Yes (indexes) | No |
| **Transactions** | No | No | Yes | No |
| **Persists** | Until cleared | Until tab closes | Until cleared | Configurable |
| **Accessible from Workers** | No | No | Yes | No |

### When to Use IndexedDB

<AccordionGroup>
  <Accordion title="Offline-First Applications">
    IndexedDB is the foundation of offline-capable apps. Store data locally so users can work without a network connection, then sync when they're back online.
    
    ```javascript
    // Cache API responses for offline use
    async function fetchWithCache(url) {
      const db = await openDB('cache', 1)
      
      // Try to get from cache first
      const cached = await db.get('responses', url)
      if (cached && !isStale(cached)) {
        return cached.data
      }
      
      // Fetch from network
      const response = await fetch(url)
      const data = await response.json()
      
      // Store in cache for next time
      await db.put('responses', { url, data, timestamp: Date.now() })
      return data
    }
    ```
  </Accordion>
  
  <Accordion title="Large Datasets">
    When you have thousands of records that would exceed localStorage's 5MB limit, IndexedDB can handle it.
    
    ```javascript
    // Store a large product catalog locally
    async function cacheProductCatalog(products) {
      const db = await openDB('shop', 1)
      const tx = db.transaction('products', 'readwrite')
      
      for (const product of products) {
        await tx.store.put(product)
      }
      
      await tx.done
      console.log(`Cached ${products.length} products`)
    }
    ```
  </Accordion>
  
  <Accordion title="Complex Querying Needs">
    When you need to search or filter data by multiple fields, indexes make this efficient.
    
    ```javascript
    // Find all products under $50
    async function getAffordableProducts(db) {
      const tx = db.transaction('products', 'readonly')
      const index = tx.store.index('price')
      const range = IDBKeyRange.upperBound(50)
      
      return await index.getAll(range)
    }
    ```
  </Accordion>
  
  <Accordion title="Storing Files and Blobs">
    Unlike localStorage, IndexedDB can store binary data like images, audio, and files.
    
    ```javascript
    // Store an image blob
    async function cacheImage(url) {
      const response = await fetch(url)
      const blob = await response.blob()
      
      const db = await openDB('images', 1)
      await db.put('images', { url, blob, cached: Date.now() })
    }
    ```
  </Accordion>
</AccordionGroup>

---

## Real-World Patterns

### Pattern 1: Sync Queue for Offline Actions

Store user actions while offline, then sync when back online:

```javascript
// Queue an action for later sync
async function queueAction(action) {
  const db = await openDB('app', 1)
  await db.add('syncQueue', {
    action,
    timestamp: Date.now(),
    status: 'pending'
  })
}

// Sync all pending actions
async function syncPendingActions() {
  const db = await openDB('app', 1)
  const pending = await db.getAllFromIndex('syncQueue', 'status', 'pending')
  
  for (const item of pending) {
    try {
      await fetch('/api/sync', {
        method: 'POST',
        body: JSON.stringify(item.action)
      })
      await db.delete('syncQueue', item.id)
    } catch (error) {
      console.log('Will retry later:', item.action)
    }
  }
}

// Sync when back online
window.addEventListener('online', syncPendingActions)
```

### Pattern 2: Database Helper Class

Encapsulate database logic in a reusable class:

```javascript
class UserDatabase {
  constructor() {
    this.dbPromise = openDB('users-db', 1, {
      upgrade(db) {
        const store = db.createObjectStore('users', { keyPath: 'id' })
        store.createIndex('email', 'email', { unique: true })
      }
    })
  }
  
  async add(user) {
    const db = await this.dbPromise
    return db.add('users', user)
  }
  
  async get(id) {
    const db = await this.dbPromise
    return db.get('users', id)
  }
  
  async getByEmail(email) {
    const db = await this.dbPromise
    return db.getFromIndex('users', 'email', email)
  }
  
  async update(user) {
    const db = await this.dbPromise
    return db.put('users', user)
  }
  
  async delete(id) {
    const db = await this.dbPromise
    return db.delete('users', id)
  }
  
  async getAll() {
    const db = await this.dbPromise
    return db.getAll('users')
  }
}

// Usage
const users = new UserDatabase()
await users.add({ id: 1, name: 'Alice', email: 'alice@example.com' })
const alice = await users.getByEmail('alice@example.com')
```

---

## Common Mistakes

### Mistake 1: Forgetting Transaction Mode

```javascript
// โŒ WRONG - Trying to write with readonly transaction
const tx = db.transaction('users')  // defaults to 'readonly'
tx.objectStore('users').add({ id: 1, name: 'Alice' })  // ERROR!

// โœ“ CORRECT - Specify 'readwrite' for write operations
const tx = db.transaction('users', 'readwrite')
tx.objectStore('users').add({ id: 1, name: 'Alice' })  // Works!
```

### Mistake 2: Creating Stores Outside onupgradeneeded

```javascript
// โŒ WRONG - Can't create stores in onsuccess
request.onsuccess = (event) => {
  const db = event.target.result
  db.createObjectStore('users')  // ERROR: Not in version change transaction
}

// โœ“ CORRECT - Create stores in onupgradeneeded
request.onupgradeneeded = (event) => {
  const db = event.target.result
  db.createObjectStore('users', { keyPath: 'id' })  // Works!
}
```

### Mistake 3: Assuming Sync Behavior

```javascript
// โŒ WRONG - Treating IndexedDB like it's synchronous
const tx = db.transaction('users', 'readwrite')
tx.objectStore('users').add({ id: 1, name: 'Alice' })
console.log('User saved!')  // This runs before the add completes!

// โœ“ CORRECT - Wait for the operation to complete
const tx = db.transaction('users', 'readwrite')
const request = tx.objectStore('users').add({ id: 1, name: 'Alice' })
request.onsuccess = () => {
  console.log('User saved!')  // Now it's actually saved
}
```

### Mistake 4: Not Handling Blocked Database Opens

When a database is open in another tab with an older version:

```javascript
const request = indexedDB.open('MyApp', 2)

// โœ“ Handle the blocked event
request.onblocked = () => {
  alert('Please close other tabs with this app to allow the update.')
}

request.onupgradeneeded = (event) => {
  // Upgrade logic
}
```

---

## Key Takeaways

<Info>
**The key things to remember about IndexedDB:**

1. **IndexedDB is a full database in the browser** โ€” it stores structured data with support for indexes, transactions, and complex queries, unlike localStorage's simple key-value pairs

2. **Everything is asynchronous** โ€” IndexedDB uses an event-based API (or Promises with a wrapper) and never blocks the main thread

3. **Object stores are like tables** โ€” each stores a collection of records identified by a unique key (either from the object's property or auto-generated)

4. **Indexes enable efficient lookups** โ€” create indexes on fields you want to query by, beyond just the primary key

5. **All operations happen in transactions** โ€” transactions ensure data integrity by grouping operations that either all succeed or all fail

6. **Transactions auto-commit quickly** โ€” never do async work (like fetch) inside a transaction; get your data first, then write to IndexedDB

7. **Use `put()` for upserts, `add()` for inserts only** โ€” `add()` fails if the key exists, `put()` inserts or updates

8. **Schema changes require version increments** โ€” only `onupgradeneeded` can create or modify object stores; increment the version number to trigger it

9. **Consider using the idb library** โ€” it wraps IndexedDB with Promises for cleaner async/await code

10. **IndexedDB is perfect for offline-first apps** โ€” store data locally, work offline, and sync when back online
</Info>

---

## Test Your Knowledge

<AccordionGroup>
  <Accordion title="Question 1: When can you create object stores in IndexedDB?">
    **Answer:**
    
    Object stores can only be created inside the `onupgradeneeded` event handler, which fires when you open a database with a higher version number than what exists. This is IndexedDB's way of handling schema migrations.
    
    ```javascript
    const request = indexedDB.open('MyApp', 2)  // Bump version to trigger upgrade
    
    request.onupgradeneeded = (event) => {
      const db = event.target.result
      db.createObjectStore('newStore', { keyPath: 'id' })  // Only works here!
    }
    ```
  </Accordion>
  
  <Accordion title="Question 2: What's the difference between add() and put()?">
    **Answer:**
    
    - `add()` inserts a new record. It **fails with an error** if a record with the same key already exists.
    - `put()` inserts a new record OR updates an existing one. It **never fails** due to duplicate keys.
    
    Use `add()` when you expect the record to be new. Use `put()` when you want "insert or update" (upsert) behavior.
  </Accordion>
  
  <Accordion title="Question 3: Why can't you use await fetch() inside a transaction?">
    **Answer:**
    
    Transactions auto-commit when there are no pending requests and the JavaScript execution returns to the event loop. A `fetch()` call is an async operation that gives control back to the event loop, causing the transaction to commit before your network request completes.
    
    ```javascript
    // โŒ Transaction closes during fetch
    const tx = db.transaction('users', 'readwrite')
    const data = await fetch('/api/user')  // Transaction closes here!
    tx.objectStore('users').add(data)  // ERROR: Transaction inactive
    
    // โœ“ Fetch first, then use IndexedDB
    const data = await fetch('/api/user')
    const tx = db.transaction('users', 'readwrite')
    tx.objectStore('users').add(data)  // Works!
    ```
  </Accordion>
  
  <Accordion title="Question 4: What are indexes used for?">
    **Answer:**
    
    Indexes let you query records by fields other than the primary key. Without an index, you'd have to iterate through every record to find matches. With an index, lookups are fast.
    
    ```javascript
    // Create an index on the 'email' field
    store.createIndex('email', 'email', { unique: true })
    
    // Later, query by email instead of primary key
    const index = store.index('email')
    const user = await index.get('alice@example.com')
    ```
  </Accordion>
  
  <Accordion title="Question 5: When should you use IndexedDB instead of localStorage?">
    **Answer:**
    
    Use IndexedDB when you need:
    - **More than 5MB** of storage
    - **Structured data** with relationships
    - **Querying capabilities** (search by different fields)
    - **To store non-string data** (objects, arrays, blobs, files)
    - **Offline-first functionality** with complex data
    - **Access from Web Workers**
    
    Use localStorage for simple key-value pairs like user preferences or small settings.
  </Accordion>
  
  <Accordion title="Question 6: What does 'readonly' vs 'readwrite' transaction mode do?">
    **Answer:**
    
    - `readonly`: You can only read data. Multiple readonly transactions can run in parallel on the same store.
    - `readwrite`: You can read and write. Only one readwrite transaction can access a store at a time (it "locks" the store).
    
    Always use `readonly` when you're just reading data. It's faster and doesn't block other transactions.
  </Accordion>
</AccordionGroup>

---

## Related Concepts

<CardGroup cols={2}>
  <Card title="localStorage & sessionStorage" icon="hard-drive" href="/beyond/concepts/localstorage-sessionstorage">
    Simpler key-value storage for smaller data. Understand when to use each option.
  </Card>
  <Card title="Promises" icon="handshake" href="/concepts/promises">
    IndexedDB's callback API is easier with Promises. Essential for using idb and other wrappers.
  </Card>
  <Card title="async/await" icon="clock" href="/concepts/async-await">
    Write cleaner IndexedDB code with async/await syntax and Promise wrappers.
  </Card>
  <Card title="Web Workers" icon="gears" href="/concepts/web-workers">
    IndexedDB works in Web Workers, enabling background data processing.
  </Card>
</CardGroup>

---

## Reference

<CardGroup cols={2}>
  <Card title="IndexedDB API โ€” MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">
    Official MDN documentation covering all IndexedDB interfaces including IDBDatabase, IDBTransaction, and IDBObjectStore
  </Card>
  <Card title="Using IndexedDB โ€” MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB">
    Comprehensive step-by-step tutorial covering the full IndexedDB workflow from opening databases to transactions
  </Card>
  <Card title="IDBObjectStore โ€” MDN" icon="book" href="https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore">
    Detailed reference for the object store interface including all CRUD methods like add(), put(), get(), and delete()
  </Card>
  <Card title="Browser Compatibility โ€” Can I Use" icon="browser" href="https://caniuse.com/indexeddb">
    Real-time browser support data showing 96%+ global coverage for IndexedDB features
  </Card>
</CardGroup>

## Articles

<CardGroup cols={2}>
  <Card title="IndexedDB Tutorial โ€” javascript.info" icon="newspaper" href="https://javascript.info/indexeddb">
    The most thorough tutorial covering versioning, object stores, transactions, cursors, and promise wrappers. Includes a working demo app with complete source code.
  </Card>
  <Card title="Work with IndexedDB โ€” web.dev" icon="newspaper" href="https://web.dev/articles/indexeddb">
    Google's official guide using the idb library with modern async/await syntax. Perfect for developers who want to skip the callback-based native API.
  </Card>
  <Card title="idb: IndexedDB with Promises" icon="newspaper" href="https://github.com/jakearchibald/idb">
    The definitive promise wrapper for IndexedDB (~1.2kB) created by Chrome engineer Jake Archibald. Makes IndexedDB feel like working with modern JavaScript.
  </Card>
  <Card title="IndexedDB Key Terminology โ€” MDN" icon="newspaper" href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Basic_Terminology">
    Explains core concepts like key paths, key generators, transactions, and the structured clone algorithm. Required reading before diving into the API.
  </Card>
</CardGroup>

## Videos

<CardGroup cols={2}>
  <Card title="IndexedDB Tutorial for Beginners โ€” dcode" icon="video" href="https://www.youtube.com/watch?v=g4U5WRzHitM">
    Clear step-by-step walkthrough of IndexedDB fundamentals including creating databases, stores, and performing CRUD operations. Great for visual learners.
  </Card>
  <Card title="IndexedDB Crash Course โ€” Traversy Media" icon="video" href="https://www.youtube.com/watch?v=vb7fkBeblcw">
    Brad Traversy's practical tutorial building a complete app with IndexedDB. Covers transactions, cursors, and real-world patterns in under 30 minutes.
  </Card>
  <Card title="Client-Side Storage Explained โ€” Fireship" icon="video" href="https://www.youtube.com/watch?v=JR9wsVYp8RQ">
    Fast-paced comparison of localStorage, sessionStorage, IndexedDB, and cookies. Helps you understand when to use each storage option.
  </Card>
  <Card title="Building Offline-First Apps โ€” Google Chrome Developers" icon="video" href="https://www.youtube.com/watch?v=cmGr0RszHc8">
    Conference talk on using IndexedDB with Service Workers for offline-capable PWAs. Essential context for understanding IndexedDB's primary use case.
  </Card>
</CardGroup>