docs-src/docs/articles/alternatives/couchbase-alternative.md
</a>
Developers building offline-capable JavaScript applications often evaluate Couchbase Mobile as a data layer because of its embedded Couchbase Lite client and its Sync Gateway component for backend replication. In practice, the stack introduces significant infrastructure requirements, a proprietary synchronization protocol, and a tight coupling to the Couchbase Server ecosystem. RxDB is a local-first JavaScript database that handles offline storage, reactive queries, and replication without any of that infrastructure dependency.
This page covers what Couchbase is, how its mobile and web stack works, where it creates friction for JavaScript developers, and how RxDB provides a simpler path to the same offline-first goals.
Couchbase has a long history in the NoSQL space. The company was formed in February 2011 through the merger of Membase (a key-value store derived from NorthScale, founded 2009) and CouchOne (a company built around Apache CouchDB development). The merger combined the high-performance, memcached-compatible architecture of Membase with the document-model concepts from CouchDB, producing a platform aimed at enterprise-scale, low-latency applications.
Over the years, Couchbase has expanded from a pure document database into a multi-model platform. The key additions:
In 2025, the company was acquired by Haveli Investments in a deal valued at approximately $1.5 billion, shifting it further toward enterprise and AI-focused positioning.
For developers building offline-capable applications, the relevant part of Couchbase is the mobile stack:
This is a capable stack for native mobile applications. For JavaScript and web development, however, it introduces a set of constraints that make it a poor fit compared to purpose-built JavaScript-native databases like RxDB.
Using Couchbase Mobile for a JavaScript application requires:
All three tiers must be version-compatible with each other. Updating any tier requires checking compatibility matrices across the stack. Sync Gateway alone requires configuration of JSON config files, RBAC roles in Couchbase Server, network port management, TLS configuration, and a custom sync function written in JavaScript that governs how documents are routed to users.
RxDB requires none of this. It runs in the browser or in Node.js directly, stores data locally, and replicates with any backend that exposes a minimal HTTP, GraphQL, or WebSocket interface. You do not need to deploy or maintain any Couchbase-specific infrastructure.
The Sync Gateway sync function is a JavaScript function that runs on the server and determines which documents each user can access. While flexible, it requires careful design and testing. An incorrect sync function can expose data to the wrong users or block replication entirely, and debugging it requires server-side log analysis.
A typical Sync Gateway configuration involves:
None of this is unreasonable for a dedicated native mobile team, but for a JavaScript application team that wants to add offline support to a web app, this is a substantial operational investment before writing a single line of application code.
Couchbase added JavaScript/browser support to Couchbase Lite in 2025. The library stores data locally using the browser's IndexedDB and can synchronize with Sync Gateway or Capella App Services. This is a meaningful addition for web developers, but several constraints remain:
Couchbase Lite for JavaScript does not support running the same database across multiple browser tabs simultaneously. If a user opens the application in two tabs, the behavior is undefined and may produce data inconsistencies. There is no cross-tab synchronization or locking mechanism.
RxDB handles this natively with its SharedWorker storage. All tabs share a single database instance running in a Web Worker. A write in one tab propagates to reactive queries in all other tabs automatically:
import { getRxStorageSharedWorker } from 'rxdb/plugins/storage-shared-worker';
const db = await createRxDatabase({
name: 'myapp',
storage: getRxStorageSharedWorker({
workerInput: new SharedWorker(
new URL('rxdb/plugins/storage-shared-worker/worker.js', import.meta.url),
{ type: 'module' }
)
})
});
The SQL++ MATCH() function for full-text search is not available in Couchbase Lite for JavaScript. Applications that require in-browser text search must implement their own solution outside of the database.
Couchbase Lite for JavaScript requires a Sync Gateway or Capella App Services backend for replication. There is no peer-to-peer synchronization between browser tabs or devices without going through the server.
RxDB supports WebRTC replication for direct peer-to-peer sync between browser clients:
import {
replicateWebRTC,
getConnectionHandlerSimplePeer
} from 'rxdb/plugins/replication-webrtc';
const replicationPool = await replicateWebRTC({
collection: db.items,
topic: 'my-collaboration-room',
connectionHandlerCreator: getConnectionHandlerSimplePeer({
signalingServerUrl: 'wss://signaling.example.com'
}),
pull: {},
push: {}
});
There is no lightweight path to synchronization. Even a simple two-user application that needs to share a handful of documents must deploy and operate a Sync Gateway instance. The Couchbase Lite protocol is proprietary, so there is no way to write a compatible backend yourself without using Couchbase's own infrastructure.
RxDB's HTTP replication works with any backend that supports two endpoints: one to fetch changed documents since a checkpoint, and one to accept pushed documents. You can implement this in any language on any infrastructure:
import { replicateRxCollection } from 'rxdb/plugins/replication';
const replicationState = replicateRxCollection({
replicationIdentifier: 'my-http-replication',
collection: db.items,
pull: {
async handler(checkpointOrNull, batchSize) {
const since = checkpointOrNull ? checkpointOrNull.updatedAt : 0;
const response = await fetch(
`/api/items?since=${since}&limit=${batchSize}`
);
const data = await response.json();
return {
documents: data.items,
checkpoint: data.checkpoint
};
}
},
push: {
async handler(rows) {
const response = await fetch('/api/items/bulk', {
method: 'POST',
body: JSON.stringify(rows)
});
return response.json(); // conflicts
}
},
live: true,
retryTime: 5000
});
RxDB is a local-first JavaScript database. All reads and writes go to local storage on the device. Replication is optional and does not affect the basic functionality of the database. An application built on RxDB works fully offline without any server connection. Sync runs in the background and applies changes when connectivity is available.
This model is identical in intent to what Couchbase Mobile aims for with Couchbase Lite. The difference is the implementation path. RxDB is a JavaScript-native library that integrates directly with the JavaScript ecosystem without requiring any server-side Couchbase components.
RxDB separates its query engine from the storage layer via the RxStorage interface. You choose the storage backend that fits your platform:
| Environment | RxDB Storage Option |
|---|---|
| Browser (general use) | IndexedDB |
| Browser (high throughput) | OPFS |
| React Native | SQLite via expo-sqlite or op-sqlite |
| Node.js / Electron | SQLite (better-sqlite3) |
| Multiple browser tabs | SharedWorker |
| Testing | Memory |
Switching storage is a single-line change in the database creation call. The rest of the application code remains identical:
import { createRxDatabase } from 'rxdb/plugins/core';
import { getRxStorageIndexedDB } from 'rxdb/plugins/storage-indexeddb';
// swap for any other storage without changing application code:
// import { getRxStorageOPFS } from 'rxdb/plugins/storage-opfs';
// import { getRxStorageSQLite } from 'rxdb/plugins/storage-sqlite';
const db = await createRxDatabase({
name: 'myapp',
storage: getRxStorageIndexedDB()
});
Couchbase Lite for JavaScript only supports IndexedDB as its browser storage backend. There is no way to switch to OPFS for better performance or to SQLite for native environments without switching to a different Couchbase Lite SDK entirely.
A key architectural difference between RxDB and Couchbase Lite is how they expose data changes to the application.
Couchbase Lite provides a change listener API that fires a callback when documents change. The application must then re-query the database to get the current state and reconcile the changes with its local state.
RxDB builds on RxJS to provide reactive queries as a first-class feature. Every query result is a live observable. When documents matching the query change (from a local write or from an incoming replication), the observable emits the updated result automatically:
// This subscription remains live and re-emits whenever matching documents change
db.items.find({
selector: { status: 'active' },
sort: [{ createdAt: 'asc' }]
}).$.subscribe(activeItems => {
// Called immediately with current results, then again on any relevant change
renderList(activeItems);
});
You can also subscribe to individual documents or to specific fields within a document:
const item = await db.items.findOne('item-001').exec();
// Fires only when the 'status' field changes
item.get$('status').subscribe(newStatus => {
updateStatusBadge(newStatus);
});
RxDB uses the event-reduce algorithm to determine whether a document change affects a query's result set without re-running the full query against storage. For most write operations, the updated result is calculated from the change event itself, making reactive queries efficient even when many subscriptions are active simultaneously.
RxDB enforces a JSON Schema on every document before it is written to storage. Documents that do not match the schema are rejected with a typed error:
await db.addCollections({
items: {
schema: {
title: 'item schema',
version: 0,
primaryKey: 'id',
type: 'object',
properties: {
id: { type: 'string', maxLength: 100 },
title: { type: 'string' },
status: { type: 'string', enum: ['active', 'done', 'archived'] },
priority: { type: 'number', minimum: 0, maximum: 10 },
createdAt: { type: 'number' }
},
required: ['id', 'title', 'status', 'priority', 'createdAt'],
indexes: ['createdAt', ['status', 'priority']]
}
}
});
RxDB also infers TypeScript types from the schema automatically. Accessing a document field gives you the exact TypeScript type without any manual type annotation:
const item = await db.items.findOne('item-001').exec();
if (item) {
// TypeScript knows: title is string, status is 'active' | 'done' | 'archived'
console.log(item.title);
console.log(item.status);
// Compile-time error if you access a field that is not in the schema
}
Couchbase Lite for JavaScript handles documents as untyped JSON objects. There is no schema declaration, no validation before writes, and no compile-time type checking on document access. All field access returns any, which removes TypeScript's ability to catch data model mismatches at development time.
RxDB's replication system is built around a protocol-agnostic pull/push model. Any backend that supports a checkpoint-based sync API can work with RxDB. Built-in plugins cover the most common cases:
This means that if your backend is already PostgreSQL, MongoDB, a REST API, or any other existing system, you can add RxDB on the client without changing the backend architecture. There is no requirement to deploy Couchbase infrastructure.
Couchbase Lite replicates exclusively using the Couchbase Lite replication protocol, which requires Sync Gateway (version 3.3.1+ or 4.0.1+) or Capella App Services on the server side. No custom backend implementation is possible. Migrating away from Couchbase on the backend means replacing the client-side database as well.
When a data model changes, RxDB handles migration automatically. You increment the schema version and provide a migration strategy for each version step. The migration runs when the database opens with the new schema version:
await db.addCollections({
items: {
schema: {
version: 1, // was 0
primaryKey: 'id',
type: 'object',
properties: {
id: { type: 'string', maxLength: 100 },
title: { type: 'string' },
status: { type: 'string', enum: ['active', 'done', 'archived'] },
priority: { type: 'number', minimum: 0, maximum: 10 },
tags: { type: 'array', items: { type: 'string' } }, // new field
createdAt: { type: 'number' }
},
required: ['id', 'title', 'status', 'priority', 'tags', 'createdAt']
},
migrationStrategies: {
1: (oldDoc) => {
// Existing documents get an empty tags array
oldDoc.tags = [];
return oldDoc;
}
}
}
});
All locally stored documents from version 0 are automatically migrated to version 1 before the application starts using the collection. Couchbase Lite has no built-in migration system. Schema changes require manual update scripts executed in application code, with no guarantee that all client devices will run them in the correct order.
RxDB provides a configurable conflict handler per collection. When a local document and a remote document conflict during replication, the handler receives both versions and returns the winning state:
await db.addCollections({
items: {
schema: itemSchema,
conflictHandler: async (input) => {
const { newDocumentState, realMasterState } = input;
// Last-write-wins based on updatedAt timestamp
if (newDocumentState.updatedAt >= realMasterState.updatedAt) {
return { documentData: newDocumentState };
}
return { documentData: realMasterState };
}
}
});
For collaborative applications where concurrent writes from multiple users should be merged automatically, RxDB supports CRDTs (Conflict-free Replicated Data Types):
import { getCRDTSchemaPart, RxDBcrdtPlugin } from 'rxdb/plugins/crdt';
import { addRxPlugin } from 'rxdb/plugins/core';
addRxPlugin(RxDBcrdtPlugin);
const itemSchema = {
version: 0,
primaryKey: 'id',
type: 'object',
properties: {
id: { type: 'string', maxLength: 100 },
title: { type: 'string' },
done: { type: 'boolean' },
crdts: getCRDTSchemaPart()
},
crdt: { field: 'crdts' }
};
With CRDTs enabled, concurrent writes are automatically merged when clients sync, with no manual conflict resolution code required for the common case.
RxDB includes a field-level encryption plugin that encrypts specific document fields before they are written to the local storage engine. The raw IndexedDB, OPFS, or SQLite contents contain ciphertext for encrypted fields:
import {
wrappedKeyEncryptionCryptoJsStorage
} from 'rxdb/plugins/encryption-crypto-js';
const db = await createRxDatabase({
name: 'myapp',
storage: wrappedKeyEncryptionCryptoJsStorage({
storage: getRxStorageIndexedDB()
}),
password: 'user-provided-passphrase'
});
const schema = {
version: 0,
primaryKey: 'id',
type: 'object',
properties: {
id: { type: 'string', maxLength: 100 },
sensitiveField: { type: 'string' }
},
encrypted: ['sensitiveField']
};
Without the passphrase, the raw storage contents are unreadable. This protects user data on shared or lost devices without requiring the application to implement custom encryption logic.
For browser applications that need high write throughput, RxDB supports the Origin Private File System (OPFS) as a storage backend. OPFS gives JavaScript direct filesystem access within the browser's sandboxed origin, bypassing IndexedDB's transaction model entirely:
import { getRxStorageOPFS } from 'rxdb/plugins/storage-opfs';
const db = await createRxDatabase({
name: 'myapp',
storage: getRxStorageOPFS()
});
OPFS provides significantly higher write throughput than IndexedDB for bulk operations because it avoids the overhead of IndexedDB transactions. Couchbase Lite for JavaScript only supports IndexedDB as its browser storage layer.
One of the practical differences between Couchbase Mobile and RxDB is what happens when requirements change.
With Couchbase Mobile:
With RxDB:
This backend flexibility is meaningful for long-lived applications where requirements evolve. A team that starts with Supabase and later moves to a custom backend does not need to replace their entire offline-first stack.
Couchbase Mobile is designed for enterprise teams building native mobile applications where the full Couchbase stack (Couchbase Server, Sync Gateway, Couchbase Lite for iOS/Android) is already established or justified by organizational requirements. The JavaScript/web support for Couchbase Lite is recent and still has constraints around multi-tab access, full-text search, and peer-to-peer sync.
RxDB is the better fit when:
| Feature | Couchbase Lite (JS) | RxDB |
|---|---|---|
| Offline-first | Yes | Yes |
| Local storage in browser | IndexedDB only | IndexedDB, OPFS, Memory |
| Local storage in React Native | Native CBL SDK (separate) | SQLite via expo-sqlite or op-sqlite |
| Reactive queries | Change listener callbacks | RxJS Observables (live query subscriptions) |
| Schema validation | No | Yes (JSON Schema) |
| TypeScript inference | No (untyped documents) | Full (inferred from schema) |
| Multi-tab browser support | Not recommended | Yes (SharedWorker storage) |
| Replication targets | Sync Gateway / Capella App Services only | Any backend (HTTP, GraphQL, WebSocket, CouchDB, Supabase, Firestore, WebRTC) |
| Backend infrastructure required | Couchbase Server + Sync Gateway | None (any existing backend works) |
| Replication protocol | Proprietary (Couchbase Lite protocol) | Open (HTTP, WebSocket, GraphQL) |
| Full-text search in browser | Not supported | Via external index or RxDB plugin |
| Peer-to-peer sync | Not supported in browser | Yes (WebRTC replication plugin) |
| Conflict resolution | Automatic (last-write-wins) | Configurable handler or CRDT plugin |
| Schema migrations | Manual | Automatic via versioned strategies |
| Encryption at rest | Enterprise feature (Couchbase EE) | Built-in field-level encryption plugin |
| Open source | Community Edition only | Yes (core + premium plugins) |
| Backend flexibility | Couchbase ecosystem only | Any backend |
| Vendor lock-in | High (Couchbase protocol) | None |
| Setup complexity | High (Server + Sync Gateway + CORS + channels) | Low (npm install, no server-side Couchbase components) |
| JavaScript-native | Recent addition (2025), limited features | Yes, built from the start for JavaScript |
| Active development | Enterprise-focused, acquired 2025 | Active, independent, commercially supported |
No. RxDB is a client-side database, not a server-side database. It runs in the browser, in React Native, in Electron, or in Node.js as an embedded database. It does not replace Couchbase Server or any other backend database. What it replaces is the client-side Couchbase Lite layer and the synchronization requirement for Sync Gateway. The backend can remain any system that exposes an API RxDB can replicate with.
</details> <details> <summary>Does RxDB require a backend to work offline?</summary>No. RxDB stores all data locally and operates fully offline without any backend connection. Replication is optional and runs in the background when connectivity is available. An application can be built with RxDB that never replicates with any backend and still has full offline-first functionality.
</details> <details> <summary>How does RxDB handle replication conflicts?</summary>RxDB uses a configurable conflict handler per collection. During replication, when a locally modified document conflicts with a version from the server, the conflict handler receives both document states and returns the winning state. Common strategies include last-write-wins (based on a timestamp field) and merge-based strategies (combining fields from both versions). For text or structured data that requires automatic merging, the CRDT plugin provides conflict-free replicated data types that merge concurrent changes without any custom handler code.
</details> <details> <summary>Can RxDB work in React Native like Couchbase Lite can?</summary>Yes. RxDB works in React Native using the SQLite storage plugin, which wraps either expo-sqlite or op-sqlite. The same schema definitions, queries, reactive subscriptions, and replication configuration that work in a browser also work in React Native. There is no separate SDK or separate configuration required.
Yes. Couchbase Lite stores data as JSON documents, and RxDB stores data as JSON documents. A migration involves reading documents from the Couchbase Lite database (using the Couchbase Lite query API), defining a matching schema in RxDB, and inserting the documents into RxDB. The primary work is defining the schema and setting up RxDB's replication to replace the Sync Gateway connection. If the backend is Couchbase Server, you would also add a new API layer (HTTP, GraphQL, or WebSocket) that RxDB can replicate with.
</details> <details> <summary>What is the licensing difference?</summary>Couchbase has a Community Edition (open source) and an Enterprise Edition (commercial). Some features, including advanced encryption and certain enterprise security options, are only available in the Enterprise Edition. Sync Gateway has its own licensing terms. RxDB's core is open source. Advanced storage plugins like IndexedDB, OPFS, and SQLite are available under a commercial premium license. The premium license is a one-time purchase and does not require ongoing cloud service fees.
</details>