Skip to main content

Replication with CouchDB

A plugin to replicate between a RxCollection and a CouchDB server.

This plugins uses the RxDB replication protocol to replicate with a CouchDB endpoint. This plugin does NOT use the official CouchDB replication protocol because the CouchDB protocol was optimized for server-to-server replication and is not suitable for fast client side applications, mostly because it has to run many HTTP-requests (at least one per document) and also it has to store the whole revision tree of the documents at the client. This makes initial replication and querying very slow.

Because the way how RxDB handles revisions and documents is very similar to CouchDB, using the RxDB replication with a CouchDB endpoint is pretty straightforward.

Pros

  • Faster initial replication.
  • Works with any RxStorage, not just PouchDB.
  • Easier conflict handling because conflicts are handled during replication and not afterwards.
  • Does not have to store all document revisions on the client, only stores the newest version.

Cons

  • Does not support the replication of attachments.
  • Like all CouchDB replication plugins, this one is also limited to replicating 6 collections in parallel. Read this for workarounds

Usage

Start the replication via replicateCouchDB().

import { replicateCouchDB } from 'rxdb/plugins/replication-couchdb';

const replicationState = replicateCouchDB(
{
replicationIdentifier: 'my-couchdb-replication',
collection: myRxCollection,
// url to the CouchDB endpoint (required)
url: 'http://example.com/db/humans',
/**
* true for live replication,
* false for a one-time replication.
* [default=true]
*/
live: true,
/**
* A custom fetch() method can be provided
* to add authentication or credentials.
* Can be swapped out dynamically
* by running 'replicationState.fetch = newFetchMethod;'.
* (optional)
*/
fetch: myCustomFetchMethod,
pull: {
/**
* Amount of documents to be fetched in one HTTP request
* (optional)
*/
batchSize: 60,
/**
* Custom modifier to mutate pulled documents
* before storing them in RxDB.
* (optional)
*/
modifier: docData => {/* ... */},
/**
* Heartbeat time in milliseconds
* for the long polling of the changestream.
* @link https://docs.couchdb.org/en/3.2.2-docs/api/database/changes.html
* (optional, default=60000)
*/
heartbeat: 60000
},
push: {
/**
* How many local changes to process at once.
* (optional)
*/
batchSize: 60,
/**
* Custom modifier to mutate documents
* before sending them to the CouchDB endpoint.
* (optional)
*/
modifier: docData => {/* ... */}
}
}
);

When you call replicateCouchDB() it returns a RxCouchDBReplicationState which can be used to subscribe to events, for debugging or other functions. It extends the RxReplicationState so any other method that can be used there can also be used on the CouchDB replication state.

Conflict handling

When conflicts appear during replication, the conflictHandler of the RxCollection is used, equal to the other replication plugins. Read more about conflict handling here.

Auth example

Lets say for authentication you need to add a bearer token as HTTP header to each request. You can achieve that by crafting a custom fetch() method that add the header field.


const myCustomFetch = (url, options) => {

// flat clone the given options to not mutate the input
const optionsWithAuth = Object.assign({}, options);
// ensure the headers property exists
if(!optionsWithAuth.headers) {
optionsWithAuth.headers = {};
}
// add bearer token to headers
optionsWithAuth.headers['Authorization'] ='Basic S0VLU0UhIExFQ0...';

// call the original fetch function with our custom options.
return fetch(
url,
optionsWithAuth
);
};

const replicationState = replicateCouchDB(
{
replicationIdentifier: 'my-couchdb-replication',
collection: myRxCollection,
url: 'http://example.com/db/humans',
/**
* Add the custom fetch function here.
*/
fetch: myCustomFetch,
pull: {},
push: {}
}
);

Also when your bearer token changes over time, you can set a new custom fetch method while the replication is running:

replicationState.fetch = newCustomFetchMethod;

Also there is a helper method getFetchWithCouchDBAuthorization() to create a fetch handler with authorization:


import {
replicateCouchDB,
getFetchWithCouchDBAuthorization
} from 'rxdb/plugins/replication-couchdb';

const replicationState = replicateCouchDB(
{
replicationIdentifier: 'my-couchdb-replication',
collection: myRxCollection,
url: 'http://example.com/db/humans',
/**
* Add the custom fetch function here.
*/
fetch: getFetchWithCouchDBAuthorization('myUsername', 'myPassword'),
pull: {},
push: {}
}
);

Known problems

Database missing

In contrast to PouchDB, this plugin does NOT automatically create missing CouchDB databases. If your CouchDB server does not have a database yet, you have to create it by yourself by running a PUT request to the database name url:

// create a 'humans' CouchDB database on the server
const remoteDatabaseName = 'humans';
await fetch(
'http://example.com/db/' + remoteDatabaseName,
{
method: 'PUT'
}
);

React Native

React Native does not have a global fetch method. You have to import fetch method with the cross-fetch package:

import crossFetch from 'cross-fetch';
const replicationState = replicateCouchDB(
{
replicationIdentifier: 'my-couchdb-replication',
collection: myRxCollection,
url: 'http://example.com/db/humans',
fetch: crossFetch,
pull: {},
push: {}
}
);