Replication with Google Drive
The replication-google-drive plugin allows you to replicate your client-side RxDB database to a folder in the user's Google Drive. This enables cross-device sync for single users without requiring any backend server.
Overview
The replication uses the Google Drive API v3 and v2.
- Offline-First: Users can work offline. Changes are synced when they go online.
- No Backend Required: You don't need to host your own database server.
- Cross-Device: Users can access their data from multiple devices by signing into the same Google account.
- Realtime Sync: Uses WebRTC for peer-to-peer signaling to achieve near real-time updates. Uses the same google-drive folder instead of a signaling-server.
Usage
Enable Google Drive API
You need to enable the Google Drive API in the Google Cloud Console and create credentials (OAuth 2.0 Client ID) for your application.
Authenticate the User
Your application must handle the OAuth flow to get an accessToken from Google. You can use libraries like @react-oauth/google or the Google Identity Services SDK.
Start Replication
Once you have the accessToken, you can start the replication.
import { replicateGoogleDrive } from 'rxdb/plugins/replication-google-drive';
const replicationState = await replicateGoogleDrive({
replicationIdentifier: 'my-app-drive-sync',
collection: myRxCollection, // RxCollection
googleDrive: {
oauthClientId: 'YOUR_GOOGLE_CLIENT_ID',
authToken: 'USER_ACCESS_TOKEN',
folderPath: 'my-app-data/user-1'
},
live: true,
pull: {
batchSize: 60,
modifier: doc => doc // (optional) modify invalid data
},
push: {
batchSize: 60,
modifier: doc => doc // (optional) modify before sending
}
});
// Observe replication states
replicationState.error$.subscribe(err => {
console.error('Replication error:', err);
});
replicationState.awaitInitialReplication().then(() => {
console.log('Initial replication done');
});Signaling & WebRTC
Google Drive does not provide real-time events for file changes. If a user changes data on User Device A, User Device B would not know about it until it periodically polls the Drive API. To achieve real-time updates, this plugin uses WebRTC to signal changes between connected devices.
- Devices create "signal files" in a
signalingsubfolder on Google Drive. - Other devices detect these files, read the WebRTC connection data, and establish a direct P2P connection with each other.
- When a device makes a write, it sends a "RESYNC" signal via WebRTC to all connected peers to notify them about the change.
Polyfill for Node.js
WebRTC is native in browsers but requires a polyfill in Node.js. Use createSimplePeerWrtc() to wrap the polyfill for compatibility with simple-peer:
import nodeDatachannelPolyfill from 'node-datachannel/polyfill';
import { createSimplePeerWrtc } from 'rxdb/plugins/replication-webrtc';
// ...
const replicationState = await replicateGoogleDrive({
// ...
signalingOptions: {
wrtc: createSimplePeerWrtc(nodeDatachannelPolyfill)
}
});Options
googleDrive
- oauthClientId
string: The OAuth 2.0 Client ID of your application. - authToken
string: The valid access token associated with the user. - folderPath
string: The path to the folder in Google Drive where data should be stored.- The plugin will ensure this folder exists.
- For the default
drivespace it must not be the root folder. - For the
appDataFolderspace it is optional and interpreted relative to the appDataFolder root. - It creates subfolders
docs(for data) andsignaling(for WebRTC).
- space
'drive' | 'appDataFolder'(optional): Defaults to'drive'. See the section below. - apiEndpoint
string(optional): Defaults tohttps://www.googleapis.com. Useful for mocking or proxies. - transactionTimeout
number(optional): Default10000(10s). The plugin uses atransactionfile in Drive to ensure data integrity during writes. This is the timeout after which a lock is considered stale.
Using the appDataFolder
By default the plugin stores data in the user visible "My Drive". Set space: 'appDataFolder' to store data in Google Drive's hidden, per-application data folder instead. This is useful on Android and other clients where you want app state synced across the user's devices without cluttering their Drive UI.
const replicationState = await replicateGoogleDrive({
replicationIdentifier: 'my-app-drive-sync',
collection: myRxCollection,
googleDrive: {
oauthClientId: 'YOUR_GOOGLE_CLIENT_ID',
authToken: 'USER_ACCESS_TOKEN',
space: 'appDataFolder'
// folderPath is optional here
},
live: true,
pull: {},
push: {}
});When using appDataFolder:
- Request the
https://www.googleapis.com/auth/drive.appdataOAuth scope when you authenticate the user. The regular Drive scopes do not grant access to this space. - The data is hidden from the user's Drive UI and cannot be browsed manually.
- The folder is isolated per OAuth client id. A debug build and a release build with different client ids will not see each other's data.
folderPathis optional. If omitted, thedocsandsignalingsubfolders are created directly in the appDataFolder root.
attachments
Controls whether binary attachment data is replicated along with documents.
- Default: enabled automatically when the collection schema has
attachments: {}defined. - Set
attachments: falseto disable attachment replication (only document fields are synced).
const replicationState = await replicateGoogleDrive({
// ...
attachments: false, // disable attachment replication
pull: {},
push: {}
});When attachment replication is enabled, attachment binary data is encoded as base64 and stored in a separate _attachments_data field inside the document's JSON file on Drive. The standard _attachments field only contains metadata stubs (digest, length, type). On pull, the base64 data is decoded back to Blob instances and written to local storage.
pull & push
Standard RxDB Replication Options for batch size, modifiers, etc.
Technical Details
File Mapping
- Each RxDB document corresponds to one JSON file in the
docssubfolder. - The filename is
[primaryKey].json. - This simple mapping makes it easy to inspect or backup data manually.
Checkpointing
- The replication relies on the
modifiedTimeof files in Google Drive.
Conflict Resolution
- Conflicts are handled using the standard RxDB conflict handling strategies.
- The plugin assumes a master-slave replication pattern where the client (RxDB) merges changes.
- If the
transactionfile is locked by another device, the write retries until the lock is released or times out.
Limitations
- Rate Limits: Google Drive API has strict rate limits. The plugin attempts to handle 429 errors with exponential backoff, but heavy concurrent writes might hit these limits.
- Latency: Changes take time to propagate and appear in listings (eventual consistency), which the plugin handles internally.
- Signaling Delay: The initial WebRTC handshake requires writing and reading files from Drive, which can take a few seconds. Once connected, signaling is instant.
Testing
For testing, it is recommended to use google-drive-mock. It simulates the Google Drive API so you can run tests without real credentials.
FAQ
What are the Google Drive API request limits?
Google Drive API rate limiting is based on quota units, not a flat request count. According to the official Google Drive API usage limits documentation, the default limits are:
- 1,000,000 quota units per minute per project
- 325,000 quota units per minute per user (within a project)
So the HTTP requests/minute depends on the endpoint cost:
- If an endpoint costs 1 quota unit/request, the theoretical maximum is 1,000,000 req/min/project and 325,000 req/min/user.
- If an endpoint costs 100 quota units/request (for example
files.listin many setups), that is about 10,000 req/min/project and 3,250 req/min/user.
When limits are exceeded, Google can return 403 (User rate limit exceeded) or 429 (Rate limit exceeded) responses.
The replication plugin already retries with exponential backoff, but for high-traffic apps you should monitor quota usage and tune your sync frequency/batch sizes.
- Official limits: https://developers.google.com/workspace/drive/api/guides/limits
- Error handling guidance: https://developers.google.com/workspace/drive/api/guides/handle-errors