TanStack DB in Electron: A Local Database for Desktop Apps
TanStack DB in Electron gives your desktop app live queries and optimistic mutations, but TanStack DB itself is an in-memory store: persistence and sync belong to the collection implementation you choose. The official @tanstack/rxdb-db-collection package puts RxDB underneath, a local-first NoSQL database with storage engines for SQLite and the Node.js filesystem and a Sync Engine for any backend. The combination is described in the TanStack DB + RxDB hub article. This page explains where the database belongs in Electron's process model, which storages the repo recommends for desktop, and how to wire a complete example with the free SQLite trial storage and useLiveQuery in the renderer.
The Two Electron Processes and Where the Database Belongsโ
An Electron runtime is divided into two parts. The main process is a Node.js process that runs without a UI in the background. The renderer processes are Chromium browser windows that render your UI, and each BrowserWindow is its own renderer process. This split decides where your data can live.
The renderer has access to the common Web Storage APIs like localStorage and IndexedDB. That is easy to set up, but IndexedDB is slow because every operation goes through layers of browser security and abstractions, and with multiple windows it becomes hard to keep the state consistent. The main process has full Node.js access, so it can open SQLite or write files directly. SQLite cannot run in the renderer at all. So the recommended architecture from the Electron database comparison is: run the storage in the main process, keep the UI in the renderer, and connect the two over IPC.
This is exactly the shape of the TanStack DB integration. RxDB persists documents through a storage in the main process. TanStack DB mirrors the collection in memory inside the renderer and runs live queries against that copy. Reads in your components never touch the disk or the IPC boundary.
Storage Options for Electronโ
RxDB abstracts persistence behind the RxStorage interface, and several storages fit the Electron main process:
- SQLite RxStorage: The recommended production option. It stores everything in a single
.sqlitefile, and since Node.js version 22 thenode:sqlitemodule ships with Node itself, which recent Electron versions include. No native module rebuilds needed. A free trial version (getRxStorageSQLiteTrial) comes with RxDB Core for evaluation. It is limited to 500 non-deleted documents, skips indexes, and runs queries in memory. The full version is part of RxDB Premium ๐. - Filesystem Node RxStorage ๐: Stores documents as plain JSON files through the Node.js filesystem API. It is a bit faster than the SQLite storage because it skips the boundary between the JavaScript process and the SQLite engine, and its setup is less complex.
- LocalStorage or IndexedDB in the renderer: Good for a quick prototype without any main-process code, slower for large datasets.
- Memory RxStorage: Keeps everything in RAM, useful for tests.
Switching storages is a configuration change, not a rewrite. You can prototype with localStorage in the renderer and move to SQLite in the main process later without touching your TanStack DB code. A deeper comparison of SQLite against the filesystem storage is in the Electron SQLite article.
Sharing One Database Between Main and Rendererโ
Hand-rolling the IPC layer means one handler per query, manual result serialization, and a self-built change-notification system so windows do not show stale state. RxDB ships this wiring as the Electron plugin. It provides two helper functions that wrap any RxStorage, similar to the Worker RxStorage:
exposeIpcMainRxStorageruns in the main process and exposes the real storage overipcMain.getRxStorageIpcRendererruns in each renderer and returns a remote RxStorage that forwards all operations to the main process overipcRenderer.
The renderer then creates a normal RxDatabase on top of that remote storage. Every write from any window lands in the main-process storage, and RxDB's change feed streams it back into the TanStack DB collections of all windows. Heavy database work never blocks the UI.
nodeIntegration must be enabled on the BrowserWindow so that the renderer can use ipcRenderer, see the Electron plugin docs.
Example: TanStack DB on RxDB with SQLite in Electronโ
The following example uses the free SQLite trial storage in the main process and a React renderer. The TanStack DB parts are identical to the hub setup, only the storage wiring is Electron-specific.
Install the Packagesโ
npm install rxdb rxjs @tanstack/react-db @tanstack/rxdb-db-collectionExpose the SQLite Storage in the Main Processโ
The trial storage ships with RxDB Core. The sqliteBasics adapter tells RxDB how to talk to the node:sqlite module that comes with Node.js 22 and newer.
// main.js (Electron main process)
import { app, ipcMain } from 'electron';
import { exposeIpcMainRxStorage } from 'rxdb/plugins/electron';
import {
getRxStorageSQLiteTrial,
getSQLiteBasicsNodeNative
} from 'rxdb/plugins/storage-sqlite';
import { DatabaseSync } from 'node:sqlite';
app.on('ready', () => {
exposeIpcMainRxStorage({
key: 'main-storage',
storage: getRxStorageSQLiteTrial({
sqliteBasics: getSQLiteBasicsNodeNative(DatabaseSync)
}),
ipcMain
});
/* ... open your BrowserWindow ... */
});Create the RxDatabase in the Rendererโ
The renderer connects to the main-process storage with the same key and defines the collection schema.
// database.ts (renderer process)
import { createRxDatabase } from 'rxdb';
import { getRxStorageIpcRenderer } from 'rxdb/plugins/electron';
import { ipcRenderer } from 'electron';
export const db = await createRxDatabase({
name: 'desktopdb',
storage: getRxStorageIpcRenderer({
key: 'main-storage',
ipcRenderer
})
});
await db.addCollections({
todos: {
schema: {
version: 0,
primaryKey: 'id',
type: 'object',
properties: {
id: { type: 'string', maxLength: 100 },
text: { type: 'string' },
completed: { type: 'boolean' }
},
required: ['id', 'text', 'completed']
}
}
});Wrap the RxCollection for TanStack DBโ
import { createCollection } from '@tanstack/react-db';
import { rxdbCollectionOptions } from '@tanstack/rxdb-db-collection';
export const todosCollection = createCollection(
rxdbCollectionOptions({
rxCollection: db.todos,
startSync: true // load the SQLite state into memory immediately
})
);Query and Mutate in Your Componentsโ
import { useLiveQuery, eq } from '@tanstack/react-db';
import { todosCollection } from './database';
export function TodoList() {
// Live query: re-renders when a matching document changes,
// also when the write came from another Electron window.
const { data: openTodos } = useLiveQuery((q) =>
q
.from({ todo: todosCollection })
.where(({ todo }) => eq(todo.completed, false))
);
return (
<ul>
{openTodos.map((todo) => (
<li
key={todo.id}
onClick={() =>
// Optimistic update: instant in the UI,
// persisted to SQLite in the main process.
todosCollection.update(todo.id, (draft) => {
draft.completed = true;
})
}
>
{todo.text}
</li>
))}
</ul>
);
}
todosCollection.insert({
id: 'todo-1',
text: 'ship the desktop app',
completed: false
});The mutation applies to the in-memory state instantly, travels over IPC into SQLite, and streams back into every subscribed window. When persisting fails, TanStack DB rolls the optimistic state back.
Switch to a Production Storageโ
The trial storage is for evaluation and prototypes only. For production, exchange the storage in the main process for the Premium SQLite RxStorage ๐ or the Filesystem Node RxStorage ๐. Nothing else changes.
// main.js, production variant
import {
getRxStorageSQLite,
getSQLiteBasicsNodeNative
} from 'rxdb-premium/plugins/storage-sqlite';
import { DatabaseSync } from 'node:sqlite';
const storage = getRxStorageSQLite({
sqliteBasics: getSQLiteBasicsNodeNative(DatabaseSync)
});Syncing the Desktop App with a Backendโ
Desktop users expect their data on other devices too. Replication is configured on the RxDB collection with replicateRxCollection() or one of the ready-made plugins for GraphQL, CouchDB, or Supabase. Writes land in SQLite first, the UI updates instantly, and the Sync Engine pushes and pulls changes whenever the network allows it. Documents pulled from the backend stream into the TanStack DB collection automatically. The general pattern is described in How to Sync TanStack DB with Your Backend.
TanStack DB's Own Electron Persistenceโ
TanStack also ships a first-party persistence adapter for Electron, the @tanstack/electron-db-sqlite-persistence package. When your app only needs collections to survive a restart on the desktop, that adapter is a valid choice and you do not need RxDB. The RxDB collection is the better fit when you want a full database under your store: replication with any backend, conflict resolution, encryption, schema migrations, and one storage abstraction that also covers your React Native and Capacitor builds.
FAQโ
Can I use TanStack DB with SQLite in Electron?
Yes. The @tanstack/rxdb-db-collection package connects TanStack DB to an RxDB collection, and the SQLite RxStorage persists that collection to a single .sqlite file in the Electron main process. A free trial version of the storage ships with RxDB Core for evaluation.
Does the database run in the Electron main or renderer process?
The storage runs in the main process and the RxDatabase runs in the renderer. The Electron plugin connects them with exposeIpcMainRxStorage and getRxStorageIpcRenderer, so database work never blocks the UI and SQLite stays where Node.js can reach it.
Do multiple Electron windows stay in sync with TanStack DB?
Yes. Each BrowserWindow connects to the same main-process storage through getRxStorageIpcRenderer with the same key. A write in one window streams through RxDB's change feed into the TanStack DB collections of all other windows, and their live queries re-render on their own.
Do I need native module rebuilds for SQLite in Electron?
No, not when you use the node:sqlite module that ships with Node.js 22 and newer, which recent Electron versions include. The getSQLiteBasicsNodeNative() adapter of the SQLite RxStorage wraps it directly. Third-party packages like sqlite3 are native addons and must be rebuilt with @electron/rebuild on every Electron upgrade.
Is there a free way to try TanStack DB with RxDB in Electron?
Yes. The SQLite trial storage comes with RxDB Core and passes the full storage test suite, limited to 500 non-deleted documents and without indexes. You can also run the free localStorage-based storage in the renderer for a prototype. For production, the full SQLite and filesystem storages are part of RxDB Premium ๐.
Follow Upโ
- Read the TanStack DB + RxDB overview for the full integration guide.
- Learn how SQLite and RxDB work together in Electron in detail.
- Compare the database options for Electron.
- Sync your desktop data with How to Sync TanStack DB with Your Backend.
- Start with the RxDB Quickstart.
- Check out the RxDB GitHub repository and leave a star โญ.
- Join the RxDB Discord to discuss your setup.