dexie、idb、localforage はすべて、ブラウザ内のデータ保存を簡単にするためのライブラリです。localforage は localStorage のような単純なキーバリュー形式を提供し、バックエンドで IndexedDB や WebSQL を自動的に選択します。一方、dexie と idb は IndexedDB に特化しており、より高度なクエリやトランザクション管理をサポートします。dexie はスキーマ管理やクエリビルダーを備えた高機能なラッパーであり、idb は IndexedDB のネイティブ API を Promise ベースに簡略化した軽量なラッパーです。
ブラウザ内でデータを保存する際、dexie、idb、localforage は代表的な選択肢ですが、それぞれ設計思想と得意とする領域が異なります。これらはすべて非同期処理をサポートしますが、データの扱い方や管理機能に大きな違いがあります。実務でどのライブラリを選ぶべきか、技術的な観点から深く比較します。
localforage は localStorage と同じキーバリュー形式を採用しています。
// localforage: 単純なキーバリュー保存
await localforage.setItem('user', { id: 1, name: 'Alice' });
const user = await localforage.getItem('user');
idb は IndexedDB のオブジェクトストアを直接使用します。
// idb: オブジェクトストアへの保存
const db = await openDB('my-db', 1, {
upgrade(db) {
db.createObjectStore('users', { keyPath: 'id' });
},
});
await db.put('users', { id: 1, name: 'Alice' });
const user = await db.get('users', 1);
dexie はテーブルとスキーマを定義できる ORM 風のアプローチを取ります。
// dexie: テーブル定義と保存
const db = new Dexie('my-db');
db.version(1).stores({ users: 'id, name' });
await db.users.put({ id: 1, name: 'Alice' });
const user = await db.users.get(1);
localforage はキーによる取得のみをサポートします。
// localforage: キー指定のみ可能
const value = await localforage.getItem('specific-key');
// 条件検索は不可(全取得してフィルタする必要がある)
idb は IndexedDB のインデックス機能を利用できます。
getAllFromIndex などで効率的に検索できます。// idb: インデックスを利用した検索
const db = await openDB('my-db');
// 'name' インデックスを使って検索
const users = await db.getAllFromIndex('users', 'name', 'Alice');
dexie は強力なクエリビルダーを提供します。
where、equals、above などのメソッドで複雑な検索を記述できます。// dexie: クエリビルダーによる検索
const users = await db.users.where('name').equals('Alice').toArray();
const adults = await db.users.where('age').above(18).toArray();
localforage はスキーマ管理機能を持ちません。
// localforage: 手動でデータ構造を管理
const data = await localforage.getItem('user');
data.newField = 'value'; // 構造変更は自己責任
await localforage.setItem('user', data);
idb は upgrade コールバックでスキーマ変更を処理します。
// idb: upgrade コールバックで管理
const db = await openDB('my-db', 2, {
upgrade(db, oldVersion, newVersion, transaction) {
if (oldVersion < 2) {
const store = transaction.objectStore('users');
store.createIndex('email', 'email');
}
},
});
dexie はバージョンチェーンによる明確なマイグレーション管理を提供します。
// dexie: バージョンチェーンで管理
db.version(1).stores({ users: 'id' });
db.version(2).stores({ users: 'id, email' }); // email インデックス追加
db.version(3).upgrade(tx => {
return tx.table('users').toCollection().modify(u => { u.role = 'user'; });
});
localforage はトランザクションを明示的に管理しません。
// localforage: 単一操作は安全だが、複数操作の保証は弱い
await localforage.setItem('a', 1);
await localforage.setItem('b', 2); // 中間で失敗すると整合性が崩れる可能性
idb はネイティブのトランザクション機能をラップしています。
transaction メソッドで複数の操作をまとめることができます。// idb: 明示的なトランザクション管理
const tx = db.transaction('users', 'readwrite');
await tx.objectStore('users').put({ id: 1 });
await tx.objectStore('logs').add({ action: 'update' });
await tx.done; // 完了を待機
dexie は操作を自動的にトランザクションでラップします。
// dexie: 自動トランザクション管理
await db.transaction('rw', db.users, db.logs, async () => {
await db.users.put({ id: 1 });
await db.logs.add({ action: 'update' });
});
| 機能 | localforage | idb | dexie |
|---|---|---|---|
| データモデル | キーバリュー | オブジェクトストア | テーブル/スキーマ |
| クエリ機能 | キー取得のみ | インデックス検索 | 豊富なクエリビルダー |
| マイグレーション | 手動 | upgrade コールバック | バージョンチェーン |
| トランザクション | 限定的 | 明示的管理 | 自動管理 |
| 学習コスト | 低 | 中 | 中 |
| 推奨用途 | 単純キャッシュ | 軽量なデータベース | 複雑なアプリケーション |
localforage は、単純なキーバリュー保存が必要な場合や、localStorage の非同期版として使いたい場合に適しています — ただし、新しい複雑なアプリでは推奨されません。IndexedDB の真の力を引き出せないため、データが増えると性能や管理面で課題が出ます。
idb は、IndexedDB の機能をそのまま使いたい開発者に最適です — 軽量で、ブラウザ標準に忠実でありながら、Promise ベースで扱いやすくなっています。余計な抽象化を好まないチームや、バンドルサイズを極限まで減らしたい場合に選択すべきです。
dexie は、本格的なクライアント側データベースが必要な場合に最適です — スキーマ管理、クエリ機能、トランザクション処理が充実しており、大規模なアプリケーション開発を支えます。データの整合性や将来の拡張性を重視するプロジェクトでは、このライブラリが最も堅牢な選択肢となります。
最終的には、プロジェクトの複雑さとチームの要件に合わせて選択してください — 単純な保存なら localforage、制御と軽量化なら idb、機能と生産性なら dexie が適しています。
複雑なデータ構造や頻繁なスキーマ変更が必要なプロジェクトには dexie を選択してください。バージョン管理機能により、データベースのアップグレードを安全に処理できます。また、高度なクエリ機能が必要な場合や、コードの可読性を重視するチームに適しています。
IndexedDB の機能を直接使用したいが、ネイティブの複雑な API は避けたい場合に idb を選択してください。軽量で依存関係が少なく、ブラウザの標準機能に忠実な実装が必要です。シンプルな保存機能だけでなく、インデックス検索も必要だが、オーバーヘッドは最小限に抑えたい場合に最適です。
単純なキーバリュー保存のみが必要で、IndexedDB の詳細を抽象化したい場合に localforage を選択してください。localStorage の非同期版として使いたい場合や、レガシーブラウザへのフォールバックが必要な古いプロジェクトの維持に適しています。ただし、新しい複雑なアプリでは他の選択肢を検討すべきです。
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