Skip to main content

TanStack DB with SQLite: Native & WASM Storage via RxDB

TanStack DB is an in-memory reactive client store with live queries and optimistic mutations, and it delegates persistence and sync to the collection type you choose. The official @tanstack/rxdb-db-collection package puts RxDB underneath, as described in the TanStack DB + RxDB overview. With the SQLite RxStorage, that RxDB layer writes your TanStack DB SQLite data into a real SQLite database: native on Node.js, Electron, React Native, and Capacitor, or compiled to WebAssembly in the browser. This page explains the two versions of the SQLite storage, lists the sqliteBasics adapters for the common SQLite bundles, walks through a runnable Node.js example, and compares the setup with TanStack DB's own SQLite persistence packages.

TanStack DB SQLite storage

Why SQLite Under TanStack DBโ€‹

SQLite is already there on most platforms where your app runs. Android and iOS ship with a built-in SQLite engine, Node.js version 22 and newer contains the node:sqlite module, and Electron apps can bundle SQLite with the main process. On mobile devices, SQLite stores data on the filesystem instead of inside browser-managed storage, so it is not subject to the cleanup rules that apply to IndexedDB. SQLite has been battle-tested for decades. It is a solid place for your local data.

TanStack DB keeps its collections in memory. When you combine it with the RxDB collection, every optimistic mutation is persisted through RxDB into SQLite, and on the next app start the TanStack DB collection loads its initial state back from the SQLite file. Changes that reach RxDB from replication or from direct RxDB code stream into the TanStack DB collection automatically. The how it works section of the hub article describes the two loops in detail.

Trial Version and Premium Versionโ€‹

The SQLite storage exists in two versions:

  • Trial version: Shipped for free with the RxDB core package. Import getRxStorageSQLiteTrial from rxdb/plugins/storage-sqlite. It passes the full RxDB storage test suite, but it is limited to 500 non-deleted documents, does not use indexes, has no attachment support, and fetches the whole storage state to run queries in memory. Use it for evaluation and prototypes only.
  • RxDB Premium ๐Ÿ‘‘ version: The production-ready storage. Import getRxStorageSQLite from rxdb-premium/plugins/storage-sqlite. It has full query support with indexes and a load of performance optimizations.

Both versions share the same API. Moving from the trial to the premium storage is an import change, not a rewrite, so you can build your whole TanStack DB integration on the trial version first.

The sqliteBasics Adaptersโ€‹

Different SQLite libraries have different APIs to open a database and run statements. Some use callbacks, some use Promises. The RxDB SQLite storage abstracts this behind a SQLiteBasics interface, and RxDB ships ready-made implementations for the common SQLite bundles:

  • getSQLiteBasicsNodeNative() for the node:sqlite module built into Node.js 22 and newer.
  • getSQLiteBasicsNode() for the sqlite3 npm package.
  • getSQLiteBasicsWasm() for wa-sqlite, SQLite compiled to WebAssembly for the browser.
  • getSQLiteBasicsQuickSQLite() for react-native-quick-sqlite in bare React Native projects.
  • getSQLiteBasicsExpoSQLiteAsync() for expo-sqlite in current Expo SDK versions, and getSQLiteBasicsExpoSQLite() for the non-async API of older Expo SDKs.
  • getSQLiteBasicsWebSQL() for react-native-sqlite-2.
  • getSQLiteBasicsCapacitor() for @capacitor-community/sqlite in Capacitor apps.
  • getSQLiteBasicsTauri() for the Tauri SQL plugin.

Notice that the storage requires SQLite version 3.38.0 or newer because it uses the SQLite JSON functions like JSON_EXTRACT to query document fields. The full code samples for every adapter are on the SQLite RxStorage page.

Example: TanStack DB on SQLite in Node.jsโ€‹

The following example runs on plain Node.js 22 or newer with the free trial storage and the built-in node:sqlite module. No native compilation step is needed.

1

Install the Packagesโ€‹

npm install rxdb rxjs @tanstack/react-db @tanstack/rxdb-db-collection

The @tanstack/react-db package exports the framework-independent createCollection(), so this also works outside of React and with the Vue, Solid, Svelte, and Angular bindings.

2

Create the RxDatabase on the SQLite Storageโ€‹

import { createRxDatabase } from 'rxdb/plugins/core';
import {
    getRxStorageSQLiteTrial,
    getSQLiteBasicsNodeNative
} from 'rxdb/plugins/storage-sqlite';
import { DatabaseSync } from 'node:sqlite';
 
const db = await createRxDatabase({
    name: 'exampledb',
    storage: getRxStorageSQLiteTrial({
        sqliteBasics: getSQLiteBasicsNodeNative(DatabaseSync)
    })
});
 
await db.addCollections({
    todos: {
        schema: {
            title: 'todos',
            version: 0,
            type: 'object',
            primaryKey: 'id',
            properties: {
                id: { type: 'string', maxLength: 100 },
                text: { type: 'string' },
                completed: { type: 'boolean' }
            },
            required: ['id', 'text', 'completed']
        }
    }
});
3

Wrap the RxCollection in a TanStack DB Collectionโ€‹

import { createCollection } from '@tanstack/react-db';
import { rxdbCollectionOptions } from '@tanstack/rxdb-db-collection';
 
const todosCollection = createCollection(
    rxdbCollectionOptions({
        rxCollection: db.todos
    })
);
4

Mutate and Observe the Durable Dataโ€‹

// Observe the RxDB collection to see writes arrive in SQLite.
db.todos.find().$.subscribe((docs) => {
    // The count grows on every run of this script
    // because the data survives the process restart.
    console.log('todos in SQLite:', docs.length);
});
 
// Optimistic mutation on the TanStack DB collection,
// persisted through RxDB into the SQLite file.
todosCollection.insert({
    id: 'todo-' + Date.now(),
    text: 'stored in SQLite',
    completed: false
});

Run the script twice. The second run starts with the todos from the first run because they live in the SQLite database, not in memory. In a UI app you would read the data with useLiveQuery as shown in the hub article.

To go to production, swap the storage import for the premium version:

import {
    getRxStorageSQLite,
    getSQLiteBasicsNodeNative
} from 'rxdb-premium/plugins/storage-sqlite';
import { DatabaseSync } from 'node:sqlite';
 
const storage = getRxStorageSQLite({
    sqliteBasics: getSQLiteBasicsNodeNative(DatabaseSync)
});

Everything else stays the same.

SQLite via WASM in the Browserโ€‹

In the browser there is no native SQLite, but the wa-sqlite package runs SQLite as WebAssembly and can persist to IndexedDB or OPFS underneath:

import {
    createRxDatabase
} from 'rxdb';
import {
    getRxStorageSQLite,
    getSQLiteBasicsWasm
} from 'rxdb-premium/plugins/storage-sqlite';
 
import SQLiteESMFactory from 'wa-sqlite/dist/wa-sqlite-async.mjs';
import SQLite from 'wa-sqlite';
const sqliteModule = await SQLiteESMFactory();
const sqlite3 = SQLite.Factory(sqliteModule);
 
const myRxDatabase = await createRxDatabase({
    name: 'exampledb',
    storage: getRxStorageSQLite({
        sqliteBasics: getSQLiteBasicsWasm(sqlite3)
    })
});

Keep in mind that SQLite via WebAssembly is slower than the IndexedDB or OPFS storages because sending data between the main thread and WASM adds latency, see the performance comparison. When you do not have a hard requirement on SQLite in the browser, follow the IndexedDB and OPFS guide instead. Your TanStack DB code does not change either way.

Mobile and Desktop Platformsโ€‹

On mobile and desktop, native SQLite is the recommended storage for RxDB and by extension for TanStack DB:

For TanStack DB with Expo SQLite, the setup looks like this:

import {
    createRxDatabase
} from 'rxdb';
import {
    getRxStorageSQLite,
    getSQLiteBasicsExpoSQLiteAsync
} from 'rxdb-premium/plugins/storage-sqlite';
import * as SQLite from 'expo-sqlite';
 
const myRxDatabase = await createRxDatabase({
    name: 'exampledb',
    multiInstance: false,
    storage: getRxStorageSQLite({
        sqliteBasics: getSQLiteBasicsExpoSQLiteAsync(SQLite.openDatabaseAsync)
    })
});

Then wrap the collections with rxdbCollectionOptions() exactly as in the Node.js example above. Notice that for Expo apps the Expo Filesystem RxStorage exists as a faster alternative to SQLite.

TanStack DB's Own SQLite Persistenceโ€‹

TanStack DB ships its own SQLite persistence layer: the @tanstack/db-sqlite-persistence-core package with platform adapters for the browser (wa-sqlite), Node.js, Electron, Expo, React Native, and Capacitor. When your only goal is to make a local-only TanStack DB collection survive a restart on one platform, those packages do the job and you do not need RxDB.

The RxDB SQLite storage covers a different scope. It puts a full database under your store, and the SQLite file becomes one part of a larger system:

FAQโ€‹

Is there a free SQLite storage for TanStack DB with RxDB?

Yes. The trial version of the SQLite RxStorage ships with the free RxDB core package as getRxStorageSQLiteTrial. It is limited to 500 non-deleted documents and is meant for evaluation and prototypes. The production version with indexes and full query support is part of RxDB Premium ๐Ÿ‘‘.

Can TanStack DB use SQLite WASM in the browser?

Yes. The premium SQLite storage supports wa-sqlite through the getSQLiteBasicsWasm() adapter, so your TanStack DB collections persist into WebAssembly SQLite. In most browser apps the IndexedDB storage or OPFS storage is the faster choice, and your TanStack DB code stays identical when you switch.

Does TanStack DB work with Expo SQLite?

Yes. The getSQLiteBasicsExpoSQLiteAsync() adapter connects the SQLite storage to the expo-sqlite module, and the TanStack DB collection sits on top through rxdbCollectionOptions(). The React Native database guide lists the recommended adapters per environment, including the faster Expo Filesystem storage.

Do I have to change my TanStack DB code when I switch from SQLite to another storage?

No. The storage is set once when the database is created, and the RxStorage interface hides it from everything above. Your TanStack DB collections, live queries, and mutations keep working unchanged on IndexedDB, OPFS, SQLite, or any other storage.

Follow Upโ€‹