Core Features
Replication and PouchDB (Offline-First)
Understand CouchDB replication and how PouchDB enables offline-first applications that sync when connectivity returns.
CouchDB Replication
CouchDB's replication is its killer feature. You can replicate:
- CouchDB to CouchDB (bi-directional)
- CouchDB to PouchDB (browser or mobile)
- Continuously (real-time sync) or one-shot
# Replicate from source to target (one-time)
curl -X POST http://admin:pass@localhost:5984/_replicate \
-H "Content-Type: application/json" \
-d '{
"source": "http://admin:pass@db1:5984/myapp",
"target": "http://admin:pass@db2:5984/myapp"
}'
# Continuous replication
curl -X POST http://admin:pass@localhost:5984/_replicate \
-d '{
"source": "myapp",
"target": "http://remote:5984/myapp",
"continuous": true
}'PouchDB: The Browser Database
PouchDB is a JavaScript database that runs in the browser (using IndexedDB) and is fully compatible with CouchDB's replication protocol. This enables the offline-first architecture:
- User's app writes to local PouchDB in the browser
- App works fully offline
- When connectivity returns, PouchDB syncs with CouchDB automatically
- Conflicts are detected and can be resolved
This pattern is extremely valuable for:
- Mobile web apps in areas with unreliable connectivity
- Field data collection applications
- Collaborative tools that need to work offline
import PouchDB from 'pouchdb';
// Local database in the browser
const localDb = new PouchDB('myapp');
// Write locally (works offline)
await localDb.put({
_id: 'note-001',
title: 'Meeting notes',
content: 'Discussed Q1 planning...',
createdAt: new Date().toISOString(),
});
// Sync with remote CouchDB (when online)
const remoteDb = new PouchDB('https://your-couch.example.com/myapp');
// One-time sync
await localDb.sync(remoteDb);
// Continuous live sync
localDb.sync(remoteDb, {
live: true,
retry: true, // Retry on network failure
}).on('change', (change) => {
console.log('Synced:', change);
}).on('error', (err) => {
console.error('Sync error:', err);
});Handling Conflicts
When the same document is modified in two places before syncing, CouchDB creates a conflict. Unlike most databases that silently overwrite, CouchDB preserves both versions and marks the document as conflicted. Your application handles the resolution.
// Detect and resolve conflicts
async function resolveConflicts(db, docId) {
const doc = await db.get(docId, { conflicts: true });
if (!doc._conflicts || doc._conflicts.length === 0) return doc;
// Fetch all conflicting revisions
const conflictingRevs = await Promise.all(
doc._conflicts.map(rev => db.get(docId, { rev }))
);
// Simple resolution: keep the most recently modified version
const winner = [doc, ...conflictingRevs].sort(
(a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)
)[0];
// Delete the losing revisions
for (const loser of conflictingRevs) {
if (loser._rev !== winner._rev) {
await db.remove(loser._id, loser._rev);
}
}
return winner;
}Mango Query (Find API)
CouchDB 2+ supports Mango Query — a MongoDB-inspired JSON query language that's more approachable than MapReduce for simple queries:
// Create an index first
await db.createIndex({ index: { fields: ['role', 'createdAt'] } });
// Query
const result = await db.find({
selector: {
role: 'admin',
createdAt: { $gt: '2025-01-01' },
},
sort: [{ createdAt: 'desc' }],
limit: 20,
});
console.log(result.docs);Example
// Offline-first notes app with PouchDB + CouchDB sync
import PouchDB from 'pouchdb';
import PouchFind from 'pouchdb-find';
PouchDB.plugin(PouchFind);
const local = new PouchDB('notes');
const remote = new PouchDB('https://couch.myapp.com/notes', {
auth: { username: 'user', password: 'pass' }
});
// Write note (works offline)
async function saveNote(note) {
return local.put({
_id: `note:${Date.now()}`,
...note,
updatedAt: new Date().toISOString(),
});
}
// Start live sync in background
local.sync(remote, { live: true, retry: true });
// Query local notes
const { docs } = await local.find({
selector: { type: 'note' },
sort: [{ updatedAt: 'desc' }],
});Want to run this code interactively?
Try in Compiler