RxDB
RxDB (Reactive Database) is a local-first, NoSQL database designed for JavaScript applications, including websites, hybrid apps, Electron, Progressive Web Apps, and Node.js. Currently stable at version 17.1.0, it follows a release cadence with frequent beta cycles leading to major versions and subsequent point releases for bug fixes. Its core differentiator is its reactive architecture, leveraging RxJS to provide observable queries and real-time data changes. RxDB supports offline-first operation, enforces data integrity through JSONSchema-based validation, and offers a highly pluggable synchronization engine for diverse backends like HTTP, GraphQL, WebRTC (P2P), CouchDB, MongoDB, Supabase, and even serverless options such as Google Drive and Microsoft OneDrive. Key features include conflict resolution (e.g., CRDTs), encryption, and robust data migration capabilities for evolving schemas and underlying storage adapters, ensuring data consistency across multiple client instances and browser tabs.
Common errors
-
RxError: DB1 (or other RxError: <CODE>)
cause RxDB throws specific error objects with codes; detailed messages are omitted in production builds for bundle size.fixIn development, enable the `RxDBDevModePlugin` (`addRxPlugin(RxDBDevModePlugin)`) to receive full, human-readable error messages including cause, fix suggestions, and documentation links. For production errors, refer to the RxDB documentation using the error code. -
TypeError: Cannot read properties of undefined (reading 'digest')
cause This error typically indicates that the JavaScript runtime environment (e.g., some WebViews, older Node.js versions) lacks support for `crypto.subtle.digest`, or access is blocked (e.g., browser running on HTTP instead of HTTPS/localhost).fixEnsure your environment supports `crypto.subtle.digest` (Node.js >=18, browser on HTTPS/localhost). Alternatively, provide a custom `hashFunction` option when creating the database if `crypto.subtle` is unavailable or problematic. -
RxError: PL3 (A plugin with the same name has already been added.)
cause You are attempting to call `addRxPlugin()` with the same plugin more than once, or there are multiple instances/versions of the same plugin in your application bundle.fixReview your application's initialization code to ensure each RxDB plugin is added only once. Check your dependency tree for duplicate package installations if this persists. -
RxError: DB6 (The schema hash does not match the schema stored in the internal database.)
cause The schema definition provided for a collection does not match the schema version already stored in the database. This usually happens when you modify a schema but forget to increment its `version` number.fixAlways increment the `version` property in your schema definition when making structural changes. If existing data needs to be transformed, define `migrationStrategies` when adding the collection to handle data updates from previous schema versions. -
RxError: COL3 (You called upsert() but the document data does not contain the primary key.)
cause The document object passed to `upsert()` or `insert()` is missing the field designated as the `primaryKey` in your collection's schema.fixEnsure that any document you insert or upsert explicitly includes the field defined as the `primaryKey` in your RxCollection's schema. This field is mandatory for document identification.
Warnings
- breaking Upgrading to RxDB v17 requires data migration for users of OPFS, Filesystem, or IndexedDB RxStorages. The underlying data storage format for IndexedDB, for example, changed to binary to reduce disk space usage. Ensure you follow the storage migration guide when updating from v16 to v17 for these specific storages.
- breaking The PouchDB RxStorage is officially deprecated and removed from the RxDB core since v15. It is no longer recommended for new projects and will not be maintained. Its use can lead to performance drawbacks and hinder access to newer RxDB features.
- breaking Since RxDB v10, a `primaryKey` is explicitly required in all RxCollection schemas. Omitting it will cause an error. Previously, RxDB would automatically use an internal `_id` field if `primaryKey` was not specified.
- gotcha The `DevMode` plugin (RxDBDevModePlugin) is essential for development as it adds comprehensive checks, validations, and human-readable error messages. However, it significantly increases bundle size and can decrease runtime performance. It must never be included in production builds.
- gotcha All RxDB plugins must be registered using `addRxPlugin()` *before* any `createRxDatabase()` call. Failing to do so will result in plugins not being activated, leading to missing functionality or errors when trying to use plugin-provided features.
- gotcha Since RxDB v10, outgoing data (e.g., results from `find()` queries) is no longer deep-cloned but assumed to be immutable. Direct mutation of these returned objects might lead to unexpected internal behavior or inconsistencies, as RxDB itself may use these references.
- gotcha When using RxDB in WebViews (e.g., within React Native or Electron mobile builds), IndexedDB-based storages can be unreliable, leading to data loss, corruption, or unexpected resets. This is due to WebView limitations regarding storage stability and background process handling.
Install
-
npm install rxdb -
yarn add rxdb -
pnpm add rxdb
Imports
- createRxDatabase
const createRxDatabase = require('rxdb'); import RxDB from 'rxdb';import { createRxDatabase } from 'rxdb'; - addRxPlugin
const addRxPlugin = require('rxdb').addRxPlugin; import { addRxPlugin } from 'rxdb/plugins/core';import { addRxPlugin } from 'rxdb'; - getRxStorageDexie
import { getRxStorageDexie } from 'rxdb';import { getRxStorageDexie } from 'rxdb/plugins/storage-dexie'; - RxJsonSchema
import { RxJsonSchema } from 'rxdb';import type { RxJsonSchema } from 'rxdb';
Quickstart
import { createRxDatabase, addRxPlugin } from 'rxdb';
import { getRxStorageDexie } from 'rxdb/plugins/storage-dexie';
import { RxDBDevModePlugin } from 'rxdb/plugins/dev-mode';
// Add dev mode plugin in development for better error messages
if (process.env.NODE_ENV !== 'production') {
addRxPlugin(RxDBDevModePlugin);
}
async function runRxDBQuickstart() {
const mySchema = {
version: 0,
primaryKey: 'id',
type: 'object',
properties: {
id: { type: 'string', maxLength: 100 },
title: { type: 'string' },
done: { type: 'boolean', default: false }
},
required: ['id', 'title', 'done']
};
const db = await createRxDatabase({
name: 'mydatabase',
storage: getRxStorageDexie(),
multiInstance: true // Enable multi-tab synchronization
});
await db.addCollections({
todos: {
schema: mySchema
}
});
// Insert a document
await db.todos.insert({
id: 'todo-1',
title: 'Learn RxDB',
done: false
});
// Query documents
const allTodos = await db.todos.find().exec();
console.log('All todos:', allTodos.map(doc => doc.toJSON()));
// Subscribe to changes (reactive query)
const subscription = db.todos.find({
selector: { done: false }
}).$.subscribe(undoneTodos => {
console.log('Undone todos changed:', undoneTodos.map(doc => doc.toJSON()));
});
// Update a document
const todoToUpdate = await db.todos.findOne('todo-1').exec();
if (todoToUpdate) {
await todoToUpdate.patch({ done: true });
}
// Wait a moment for subscription to react and then clean up
setTimeout(async () => {
subscription.unsubscribe();
await db.destroy();
console.log('Database destroyed.');
}, 1000);
}
runRxDBQuickstart();