CouchDB and MongoDB aren’t just different databases; they represent fundamentally opposing philosophies on how to manage data in a distributed, eventually consistent world.

Let’s see CouchDB in action, specifically its replication and conflict resolution. Imagine two identical CouchDB instances, couch1 and couch2, each running on localhost with default ports.

On couch1, let’s create a database called my_app and add a document:

curl -X PUT http://localhost:5984/my_app
curl -X POST -H "Content-Type: application/json" -d '{"name": "Alice", "city": "New York"}' http://localhost:5984/my_app

Now, let’s set up a continuous replication from couch1 to couch2. This means couch2 will pull changes from couch1 automatically.

On couch2, we’d create a replication document within the _replicator database. This document tells CouchDB to replicate my_app from couch1 to couch2 continuously.

curl -X POST -H "Content-Type: application/json" -d '{
  "source": "http://localhost:5984/my_app",
  "target": "http://localhost:5984/my_app",
  "continuous": true
}' http://localhost:5984/_replicator

If you check the replication status on couch2 (e.g., by querying _replicator/_all_docs?include_docs=true), you’ll see it’s active. Now, any document added or modified on couch1 will appear on couch2 shortly, and vice-versa.

The real magic happens with conflicts. Let’s modify the same document on both instances simultaneously, without them knowing about each other yet.

On couch1:

# First, get the document's current revision ID
curl http://localhost:5984/my_app/alice_doc_id
# Let's say the rev is "1-abc..."
# Now update it
curl -X PUT -H "Content-Type: application/json" -d '{"_rev": "1-abc...", "name": "Alice Wonderland", "city": "London"}' http://localhost:5984/my_app/alice_doc_id

On couch2 (before it pulls the update from couch1):

# Get the document's current revision ID (it will be "1-abc...")
# Now update it
curl -X PUT -H "Content-Type: application/json" -d '{"_rev": "1-abc...", "name": "Alice", "city": "Paris"}' http://localhost:5984/my_app/alice_doc_id

Once replication kicks in, CouchDB will detect that the same document has been modified on two different "sides" of the replication. Instead of arbitrarily picking one and discarding the other, CouchDB creates a conflict. The document will now have multiple revision IDs, and you’ll see a _conflicts field in the document’s metadata.

To resolve this, you typically write an update handler (a JavaScript function stored in CouchDB) that knows how to merge these conflicting versions. This handler runs when a conflict is detected. It might compare timestamps, prioritize certain sources, or apply custom logic to create a single, new winning revision. This is CouchDB’s core strength: explicit, application-level conflict resolution.

MongoDB, on the other hand, prioritizes strong consistency within a replica set. When a write occurs, the primary node in a replica set is responsible for ensuring that write is acknowledged by a majority of secondaries before it’s considered committed. If the primary fails, a new primary is elected, and it starts serving reads and writes only after it has caught up sufficiently with the oplog (the log of operations) from the old primary. This "leader-based" approach means there’s a single source of truth at any given moment, and conflicts as CouchDB understands them (multiple concurrent writers to the same logical document without a central arbiter) are avoided by design. MongoDB’s consistency model is geared towards preventing data loss or divergence in the first place.

The most surprising true thing about CouchDB’s replication is that it’s fundamentally a peer-to-peer system where every node can be both a source and a target, and replication is driven by the documents themselves and their revision history, not by a central master. This is why it excels in offline-first scenarios and mobile applications where network partitions are common.

The way CouchDB handles document revisions is crucial. Every time you update a document, CouchDB generates a new revision ID. This ID is a SHA-1 hash of the document’s content plus the parent revision ID. This forms a linked list of revisions, allowing CouchDB to efficiently track changes and, importantly, to detect and resolve conflicts. When two replicas diverge, CouchDB can compare these revision chains to pinpoint exactly where the divergence occurred and present the conflicting branches to your application for resolution.

The _conflicts field in a document’s metadata isn’t just a flag; it’s a list of the winning revision IDs that are in conflict. To resolve a conflict, you typically fetch all conflicting revisions, merge them into a new, single document, and then PUT that new document back into CouchDB. The _rev parameter for this PUT request must be one of the conflicting revision IDs. CouchDB then creates a new revision based on your merged document, effectively "winning" the conflict.

The next concept you’ll likely encounter is designing efficient update handlers and understanding the implications of CouchDB’s eventually consistent nature on application design.

Want structured learning?

Take the full Couchdb course →