RxDB
A reactive, offline-first Database for JavaScript
Features
- Multiplatform support for browsers, nodejs, electron, cordova, react-native and every other javascript-runtime
- Reactive data-handling based on rxjs
- Replication between client and server-data, compatible with PouchDB, CouchDB and IBM Cloudant
- Schema-based with the easy-to-learn standard of jsonschema
- Mango-Query exactly like you know from mongoDB and mongoose
- Encryption of single data-fields to protect your users data
- Import/Export of the database-state (json), awesome for coding with TDD
- Multi-Window to synchronise data between different browser-windows or nodejs-processes
- ORM-capabilities to easily handle data-code-relations
Platform-support
RxDB is made so that you can use exactly the same code at
-
browsers
- NodeJS
- Electron
- React-Native
- Cordova/Phonegap
- Nativescript
We optimized, double-checked and made boilerplates so you can directly start to use RxDB with frameworks like
- react
- angular/ng2
- ionic2
- vuejs
Quickstart
Installation:
npm install rxdb --save
npm install rxjs babel-polyfill --save
Import:
ES7
import 'babel-polyfill';
import RxDB from 'rxdb';
const db = await RxDB.create({
name: 'heroesdb',
adapter: 'websql',
password: 'myLongAndStupidPassword',
multiInstance: true
});
await db.collection({name: 'heroes', schema: mySchema});
db.heroes.insert({ name: 'Bob' });
ES5
require('babel-polyfill');
var RxDB = require('rxdb');
RxDB.create({
name: 'heroesdb',
adapter: 'websql',
password: 'myLongAndStupidPassword',
multiInstance: true
})
.then(function(db) {return db.collection({name: 'heroes', schema: mySchema});})
.then(function(collection) {collection.insert({name: 'Bob'});})
Feature-Showroom (click to toggle)
Mango-Query
To find data in your collection, use the mquery api to create chained mango-queries, which you maybe know from mongoDB or mongoose.
myCollection
.find()
.where('name').ne('Alice')
.where('age').gt(18).lt(67)
.limit(10)
.sort('-age')
.exec().then( docs => {
console.dir(docs);
});
Reactive
RxDB implements rxjs to make your data reactive.
This makes it easy to always show the real-time database-state in the dom without manually re-submitting your queries.
db.heroes
.find()
.sort('name')
.$
.subscribe( docs => {
myDomElement.innerHTML = docs
.map(doc => '<li>' + doc.name + '</li>')
.join();
});
MultiWindow/Tab
When two instances of RxDB use the same storage-engine, their state and action-stream will be broadcasted.
This means with two browser-windows the change of window #1 will automatically affect window #2. This works completely offline.
Replication
Because RxDB relies on glorious PouchDB, it is easy to replicate
the data between devices and servers. And yes, the changeEvents are also synced.
Schema
Schemas are defined via jsonschema and are used to describe your data.
const mySchema = {
title: "hero schema",
version: 0,
description: "describes a simple hero",
type: "object",
properties: {
name: {
type: "string",
primary: true
},
secret: {
type: "string",
encrypted: true
},
skills: {
type: "array",
maxItems: 5,
uniqueItems: true,
item: {
type: "object",
properties: {
name: {
type: "string"
},
damage: {
type: "number"
}
}
}
}
},
required: ["color"]
};
Encryption
By setting a schema-field to encrypted: true
, the value of this field will be stored in encryption-mode and can't be read without the password. Of course you can also encrypt nested objects. Example:
"secret": {
"type": "string",
"encrypted": true
}
Level-adapters
The underlying pouchdb can use different adapters as storage engine. So you can use RxDB in different environments by just switching the adapter.
For example you can use websql in the browser, localstorage in mobile-browsers and a leveldown-adapter in nodejs.
RxDB.plugin(require('pouchdb-adapter-localstorage'));
const db = await RxDB.create('heroesDB', 'localstorage');
Some adapters you can use:
Import / Export
RxDB lets you import and export the whole database or single collections into json-objects. This is helpful to trace bugs in your application or to move to a given state in your tests.
const jsonCol = await myCollection.dump();
const jsonDB = await myDatabase.dump();
await emptyCollection.importDump(json);
await emptyDatabase.importDump(json);
Leader-Election
Imagine your website needs to get a piece of data from the server once every minute. To accomplish this task
you create a websocket or pull-interval. If your user now opens the site in 5 tabs parallel, it will run the interval
or create the socket 5 times. This is a waste of resources which can be solved by RxDB's LeaderElection.
myRxDatabase.waitForLeadership()
.then(() => {
mySocket = createWebSocket();
});
In this example the leader is marked with the crown ♛
Key-Compression
Depending on which adapter and in which environment you use RxDB, client-side storage is limited in some way or the other. To save disc-space, RxDB has an internal schema-based key-compression to minimize the size of saved documents.
Example:
await myCollection.insert({
firstName: 'foo'
lastName: 'bar'
stupidLongKey: 5
});
{
'|a': 'foo'
'|b': 'bar'
'|c': 5
}
console.log(myDoc.firstName);
QueryChangeDetection
Similar to Meteors oplog-observe-driver,
RxDB has a QueryChangeDetection to optimize observed or reused queries. This makes sure that when you update/insert/remove documents,
the query does not have to re-run over the whole database but the new results will be calculated from the events. This creates a huge performance-gain
with zero cost. The QueryChangeDetection works internally and is currently in beta (disabled by default).
Use-Case-Example
Imagine you have a very big collection with many user-documents. At your page you want to display a toplist with users which have the most points
and are currently logged in.
You create a query and subscribe to it.
const query = usersCollection.find().where('loggedIn').eq(true).sort('points');
query.$.subscribe(users => {
document.querySelector('body').innerHTML = users
.reduce((prev, cur) => prev + cur.username+ '<br/>', '');
});
As you may detect, the query can take very long time to run, because you have thousands of users in the collection.
When a user now logs off, the whole query will re-run over the database which takes again very long.
anyUser.loggedIn = false;
await anyUser.save();
But not with the QueryChangeDetection enabled.
Now, when one user logs off, it will calculate the new results from the current results plus the RxChangeEvent. This often can be done in-memory without making IO-requests to the storage-engine. The QueryChangeDetection not only works on subscribed queries, but also when you do multiple .exec()
's on the same query.
Browser support
All major evergreen browsers and IE11 are supported. Tests automatically run against Firefox and Chrome, and manually in a VirtualBox for IE11 and Edge.
As RxDB heavily relies on PouchDB, see their browser support for more information. Also do keep in mind that different browsers have different storage limits, especially on mobile devices.
Getting started
Get started now by reading the docs or exploring the example-projects.
You can test out RxDB in the browser with WebpackBin.
Contribute
Check out how you can contribute to this project.
Follow up
- Follow RxDB on twitter to not miss the latest enhancements.
- Join the chat on gitter for discussion.
Thank you
A big Thank you to every supporter and every contributor of this project.