dexie、idb-keyval、localforage はいずれもブラウザの IndexedDB をより使いやすくするための JavaScript ライブラリです。IndexedDB は強力なクライアントサイドデータベースですが、低レベルで冗長な API のため、これらのライブラリは開発者の生産性を高めることを目的としています。dexie はフル機能のクエリエンジンとトランザクション制御を備えた ORM に近い設計です。idb-keyval は単純な key-value ストアとして動作し、最小限の API で即座に利用できます。localforage は localStorage に似たインターフェースを提供しつつ、バックエンドに IndexedDB(または WebSQL / localStorage)を使用することで、非同期かつ大容量のストレージを実現します。
dexie、idb-keyval、localforage はすべて IndexedDB をラップして使いやすくするライブラリですが、設計思想と用途が大きく異なります。それぞれの特徴を、実際のコードを交えて詳しく見ていきましょう。
dexie は明示的なテーブル定義とスキーマを持つ、本格的なデータベースライクな設計です。
// dexie: テーブルとインデックスの定義
import Dexie from 'dexie';
const db = new Dexie('MyApp');
db.version(1).stores({
friends: '++id, name, age', // 自動インクリメントID、name と age にインデックス
notes: '++id, title, &body' // body にユニークインデックス
});
idb-keyval は完全な key-value ストアで、1 つのストア内に任意のキーと値を保存します。
// idb-keyval: スキーマ不要、即時使用
import { get, set } from 'idb-keyval';
await set('user', { name: 'Alice', age: 30 });
const user = await get('user');
localforage も key-value モデルですが、複数の「インスタンス」で名前空間を分離できます。
// localforage: 名前付きインスタンス
import localforage from 'localforage';
const userStore = localforage.createInstance({ name: 'users' });
await userStore.setItem('alice', { age: 30 });
const alice = await userStore.getItem('alice');
dexie は豊富なクエリメソッドを提供します。
between)、部分一致(startsWith)、複合条件(and/or)など// dexie: 複雑なクエリ
const adults = await db.friends
.where('age')
.above(18)
.toArray();
const names = await db.friends
.where('name')
.startsWith('A')
.keys();
idb-keyval はキーによる直接取得のみをサポートします。
values())やキー一覧(keys())は可能だが、フィルタリングは自前実装// idb-keyval: キー指定での取得のみ
import { get, keys } from 'idb-keyval';
const user = await get('user-123');
// 条件検索はできない
localforage も同様に、キーによる取得と全件取得のみ。
keys() で全キーを取得後、自前でフィルタリングする必要あり// localforage: キーによる取得
const user = await localforage.getItem('user-123');
// 複雑な検索は不可能
dexie は明示的なトランザクション制御をサポートします。
// dexie: トランザクション
await db.transaction('rw', db.friends, db.notes, async () => {
await db.friends.add({ name: 'Bob' });
await db.notes.add({ title: 'Note for Bob' });
});
idb-keyval と localforage はトランザクションを公開していません。
// idb-keyval / localforage: トランザクションなし
await set('a', 1);
await set('b', 2);
// この2操作はアトミックではない
dexie は Promise ベースで、チェーン可能なクエリビルダーを提供します。
// dexie: Promise チェーン
const result = await db.friends
.where('age')
.above(25)
.sortBy('name');
idb-keyval も Promise 専用で、極めてシンプルな関数群です。
// idb-keyval: 単純な Promise 関数
const value = await get('key');
localforage は Promise とコールバックの両方をサポートします。
// localforage: 両方のスタイル
// Promise
const value = await localforage.getItem('key');
// コールバック
localforage.getItem('key', (err, value) => {
if (!err) console.log(value);
});
dexie と idb-keyval は IndexedDB にのみ依存します。
localforage は複数のストレージバックエンドを自動で切り替えます。
// localforage: バックエンド自動選択
// 開発者は意識する必要なし
dexie// dexie での例
db.version(1).stores({
todos: '++id, listId, completed, priority',
lists: '++id, name'
});
// 高優先度の未完了タスクを取得
const highPriority = await db.todos
.where('priority')
.equals('high')
.and(todo => !todo.completed)
.toArray();
idb-keyval// idb-keyval での例
await set('authToken', 'abc123');
const token = await get('authToken');
localStorage.setItem で書かれたコードがあるlocalforage// localforage での置き換え
// localStorage.setItem('user', JSON.stringify(user));
await localforage.setItem('user', user); // シリアライズ不要
| 特徴 | dexie | idb-keyval | localforage |
|---|---|---|---|
| データモデル | テーブル+インデックス | 単一 key-value | key-value(名前空間可) |
| クエリ能力 | 高度(範囲、部分一致等) | キー取得のみ | キー取得のみ |
| トランザクション | 明示的サポート | なし | なし |
| API スタイル | Promise + チェーン | Promise 関数 | Promise + コールバック |
| バックエンド | IndexedDB のみ | IndexedDB のみ | IndexedDB/WebSQL/localStorage |
| bundle size | やや大きめ | 極小 | 中程度 |
dexieidb-keyvallocalforageこれら3つのライブラリは、それぞれ異なる「痛み」を解決するために作られています。自分のプロジェクトが抱える課題に最もフィットするものを選ぶことが、長期的なメンテナンス性と開発速度の鍵になります。
dexie は複雑なクエリ(範囲検索、インデックス、複合キーなど)やトランザクション制御が必要なアプリケーションに最適です。オフラインファーストの PWA や大量の構造化データを扱う場合に強みを発揮します。ただし、学習コストがやや高く、シンプルな key-value 用途にはオーバーキルになる可能性があります。
idb-keyval は「ただ値を保存して取り出したい」だけのケースに最適です。API が localStorage に似ており、わずか 4 つの関数(get、set、del、clear)しか持たないため、導入・理解が極めて容易です。複雑なクエリやスキーマ管理は不要で、軽量さとシンプルさを重視するプロジェクトに適しています。
localforage は既存の localStorage コードを非同期かつ大容量対応に置き換えたい場合に最適です。コールバックと Promise の両方をサポートし、バックエンドを自動で切り替えるため、古いブラウザとの互換性も考慮できます。ただし、IndexedDB の高度な機能(カスタムインデックス、複雑なクエリ)は利用できません。
Dexie.js is a wrapper library for indexedDB - the standard database in the browser. https://dexie.org.
IndexedDB is the portable database for all browser engines. Dexie.js makes it fun and easy to work with.
But also:
<!DOCTYPE html>
<html>
<head>
<script type="module">
// Import Dexie
import { Dexie } from 'https://unpkg.com/dexie/dist/modern/dexie.mjs';
//
// Declare Database
//
const db = new Dexie('FriendDatabase');
db.version(1).stores({
friends: '++id, age'
});
//
// Play with it
//
try {
await db.friends.add({ name: 'Alice', age: 21 });
const youngFriends = await db.friends
.where('age')
.below(30)
.toArray();
alert(`My young friends: ${JSON.stringify(youngFriends)}`);
} catch (e) {
alert(`Oops: ${e}`);
}
</script>
</head>
</html>
Yes, it's that simple. Read the docs to get into the details.
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/dexie/dist/dexie.js"></script>
<script>
//
// Declare Database
//
const db = new Dexie('FriendDatabase');
db.version(1).stores({
friends: '++id, age'
});
//
// Play with it
//
db.friends.add({ name: 'Alice', age: 21 }).then(() => {
return db.friends
.where('age')
.below(30)
.toArray();
}).then(youngFriends => {
alert (`My young friends: ${JSON.stringify(youngFriends)}`);
}).catch (e => {
alert(`Oops: ${e}`);
});
</script>
</head>
</html>
Real-world apps are often built using components in various frameworks. Here's a version of Hello World written for React and Typescript. There are also links below this sample to more tutorials for different frameworks...
import React from 'react';
import { Dexie, type EntityTable } from 'dexie';
import { useLiveQuery } from 'dexie-react-hooks';
// Typing for your entities (hint is to move this to its own module)
export interface Friend {
id: number;
name: string;
age: number;
}
// Database declaration (move this to its own module also)
export const db = new Dexie('FriendDatabase') as Dexie & {
friends: EntityTable<Friend, 'id'>;
};
db.version(1).stores({
friends: '++id, age',
});
// Component:
export function MyDexieReactComponent() {
const youngFriends = useLiveQuery(() =>
db.friends
.where('age')
.below(30)
.toArray()
);
return (
<>
<h3>My young friends</h3>
<ul>
{youngFriends?.map((f) => (
<li key={f.id}>
Name: {f.name}, Age: {f.age}
</li>
))}
</ul>
<button
onClick={() => {
db.friends.add({ name: 'Alice', age: 21 });
}}
>
Add another friend
</button>
</>
);
}
Tutorials for React, Svelte, Vue, Angular and vanilla JS
Dexie has kick-ass performance. Its bulk methods take advantage of a lesser-known feature in IndexedDB that makes it possible to store stuff without listening to every onsuccess event. This speeds up the performance to a maximum.
above(key): Collection;
aboveOrEqual(key): Collection;
add(item, key?): Promise;
and(filter: (x) => boolean): Collection;
anyOf(keys[]): Collection;
anyOfIgnoreCase(keys: string[]): Collection;
below(key): Collection;
belowOrEqual(key): Collection;
between(lower, upper, includeLower?, includeUpper?): Collection;
bulkAdd(items: Array): Promise;
bulkDelete(keys: Array): Promise;
bulkPut(items: Array): Promise;
clear(): Promise;
count(): Promise;
delete(key): Promise;
distinct(): Collection;
each(callback: (obj) => any): Promise;
eachKey(callback: (key) => any): Promise;
eachPrimaryKey(callback: (key) => any): Promise;
eachUniqueKey(callback: (key) => any): Promise;
equals(key): Collection;
equalsIgnoreCase(key): Collection;
filter(fn: (obj) => boolean): Collection;
first(): Promise;
get(key): Promise;
inAnyRange(ranges): Collection;
keys(): Promise;
last(): Promise;
limit(n: number): Collection;
modify(changeCallback: (obj: T, ctx:{value: T}) => void): Promise;
modify(changes: { [keyPath: string]: any } ): Promise;
noneOf(keys: Array): Collection;
notEqual(key): Collection;
offset(n: number): Collection;
or(indexOrPrimayKey: string): WhereClause;
orderBy(index: string): Collection;
primaryKeys(): Promise;
put(item: T, key?: Key): Promise;
reverse(): Collection;
sortBy(keyPath: string): Promise;
startsWith(key: string): Collection;
startsWithAnyOf(prefixes: string[]): Collection;
startsWithAnyOfIgnoreCase(prefixes: string[]): Collection;
startsWithIgnoreCase(key: string): Collection;
toArray(): Promise;
toCollection(): Collection;
uniqueKeys(): Promise;
until(filter: (value) => boolean, includeStopEntry?: boolean): Collection;
update(key: Key, changes: { [keyPath: string]: any }): Promise;
This is a mix of methods from WhereClause, Table and Collection. Dive into the API reference to see the details.
Dexie Cloud is the easiest way to add sync, authentication, and real-time collaboration to your Dexie app. You keep writing frontend code with Dexie.js — Dexie Cloud handles the rest.
What you get:
Getting started is just a few lines:
npm install dexie-cloud-addon
import Dexie from 'dexie';
import dexieCloud from 'dexie-cloud-addon';
const db = new Dexie('MyDatabase', { addons: [dexieCloud] });
db.version(1).stores({ items: '@id, title' });
db.cloud.configure({ databaseUrl: 'https://<your-db>.dexie.cloud' });
That's it. Your existing Dexie app now syncs. Hosted cloud or self-hosted on your own infrastructure. 👋
Sample app:
Source: Dexie Cloud To-do app
Live demo: https://dexie.github.io/Dexie.js/dexie-cloud-todo-app/
https://dexie.org/docs/Samples
https://github.com/dexie/Dexie.js/tree/master/samples
https://dexie.org/docs/Questions-and-Answers
npm install dexie
For those who don't like package managers, here's the download links:
https://unpkg.com/dexie@latest/dist/dexie.min.js
https://unpkg.com/dexie@latest/dist/dexie.min.js.map
https://unpkg.com/dexie@latest/dist/modern/dexie.min.mjs
https://unpkg.com/dexie@latest/dist/modern/dexie.min.mjs.map
https://unpkg.com/dexie@latest/dist/dexie.d.ts
See CONTRIBUTING.md
pnpm install
pnpm run build
pnpm test
pnpm run watch