docs/replication-firestore.md
Leverage RxDB to enable real-time, offline-first replication with Firestore. Cut cloud costs, resolve conflicts, and speed up your app.
import {Steps} from '@site/src/components/steps'; import {HeadlineWithIcon} from '@site/src/components/headline-with-icon';
With the replication-firestore plugin you can do a two-way realtime replication
between your client side RxDB Database and a Cloud Firestore database that is hosted on the Firebase platform. It will use the RxDB Sync Engine to manage the replication streams, error- and conflict handling.
Replicating your Firestore state to RxDB can bring multiple benefits compared to using the Firestore directly:
npm install firebase
import * as firebase from 'firebase/app';
import {
getFirestore,
collection
} from 'firebase/firestore';
const projectId = 'my-project-id';
const app = firebase.initializeApp({
projectId,
databaseURL: 'http://localhost:8080?ns=' + projectId,
/* ... */
});
const firestoreDatabase = getFirestore(app);
const firestoreCollection = collection(firestoreDatabase, 'my-collection-name');
Start the replication by calling replicateFirestore() on your RxCollection.
const replicationState = replicateFirestore({
replicationIdentifier: `https://firestore.googleapis.com/${projectId}`,
collection: myRxCollection,
firestore: {
projectId,
database: firestoreDatabase,
collection: firestoreCollection
},
/**
* (required) Enable push and pull replication with firestore by
* providing an object with optional filter
* for each type of replication desired.
* [default=disabled]
*/
pull: {},
push: {},
/**
* Either do a live or a one-time replication
* [default=true]
*/
live: true,
/**
* (optional) likely you should just use the default.
*
* In firestore it is not possible to read out
* the internally used write timestamp of a document.
* Even if we could read it out, it is not indexed which
* is required for fetch 'changes-since-x'.
* So instead we have to rely on a custom user defined field
* that contains the server time
* which is set by firestore via serverTimestamp()
* Notice that the serverTimestampField MUST NOT be
* part of the collections RxJsonSchema!
* [default='serverTimestamp']
*/
serverTimestampField: 'serverTimestamp'
});
To observe and cancel the replication, you can use any other methods from the ReplicationState like error$, cancel() and awaitInitialReplication().
RxDB requires you to never fully delete documents. This is needed to be able to replicate the deletion state of a document to other instances. The firestore replication will set a boolean _deleted field to all documents to indicate the deletion state. You can change this by setting a different deletedField in the sync options.
enableIndexedDbPersistence()Firestore has the enableIndexedDbPersistence() feature which caches document states locally to IndexedDB. This is not needed when you replicate your Firestore with RxDB because RxDB itself will store the data locally already.
If you have not used RxDB before and you already have documents inside of your Firestore database, you have
to manually set the _deleted field to false and the serverTimestamp to all existing documents.
import {
getDocs,
query,
where,
serverTimestamp
} from 'firebase/firestore';
const allDocsResult = await getDocs(query(firestoreCollection));
allDocsResult.forEach(doc => {
doc.update({
_deleted: false,
serverTimestamp: serverTimestamp()
})
});
Also notice that if you do writes from non-RxDB applications, you have to keep these fields in sync. It is recommended to use the Firestore triggers to ensure that.
You might need to replicate only a subset of your collection, either to or from Firestore. You can achieve this using push.filter and pull.filter options.
const replicationState = replicateFirestore(
{
collection: myRxCollection,
firestore: {
projectId,
database: firestoreDatabase,
collection: firestoreCollection
},
pull: {
filter: [
where('ownerId', '==', userId)
]
},
push: {
filter: (item) => item.syncEnabled === true
}
}
);
Keep in mind that you can not use inequality operators (<, <=, !=, not-in, >, or >=) in pull.filter since that would cause a conflict with ordering by serverTimestamp.