Apache CouchDB

Apache CouchDB is the original document database. Created by Damien Katz (ex-Lotus Notes) in 2005 and donated to the Apache Software Foundation in 2008, CouchDB pioneered JSON-document storage years before MongoDB and shipped one of the first multi-master replication models in any open database. CouchDB is the database that made “offline-first” applications possible — a mobile or browser client can read, write, and synchronize against a master database without ever blocking on the network.

CouchDB's design follows from one goal: data should be able to live on many machines that are not always connected, and still converge. Every document carries a revision history, every database can replicate with any other over HTTP, and conflicts are kept rather than discarded so the application can decide how to merge them. The same protocol is implemented by PouchDB in the browser and by Couchbase Lite on mobile devices, which is what makes offline-first sync practical.

Architecture and replication

Multi-master replication between CouchDB peers Browser / phone PouchDB or Couchbase Lite HTTP _replicate changes feed, revs_diff, bulk_docs CouchDB cluster shards (q) x replicas (n) HTTP _replicate continuous, resumable Second site another CouchDB, offline for days Every peer accepts writes. Replication copies missing revisions in both directions and resumes from a checkpoint.

Replication is a client of the ordinary HTTP API: it reads the source's _changes feed from its last checkpoint, asks the target which revisions it is missing (_revs_diff), and copies them with _bulk_docs. Because it is incremental and resumable, a device that was offline for a week only transfers what changed. Within a cluster, CouchDB 2.x and later split each database into q shards and keep n copies of each (default q=8 in CouchDB 2.x and q=2 since 3.0, with n=3 copies; both can be set per database at creation, e.g. PUT /db?q=4&n=3), using quorum reads and writes across nodes.

Key Features:

CouchDB vs. MongoDB:

Use Cases:

Revisions and conflicts

Two offline edits of the same document Both peers start with 1-a3f Laptop edits offline 1-a3f 2-b71 Phone edits offline 1-a3f 2-c09 After replication (both peers) 1-a3f 2-b71 2-c09 App merges, deletes loser 1-a3f 3-d42 The winner is chosen deterministically, so every peer shows the same revision; the loser is kept until resolved.

A revision id is a generation number plus a hash of the content. Every update must quote the current _rev; if someone else updated first, the write fails with 409 Conflict, which is optimistic concurrency on a single node. Across replicas, two peers can each accept an update to the same revision; after replication the document has two leaf revisions. CouchDB picks the same winner on every node (highest generation, then highest hash), returns it on normal reads, and exposes the others through ?conflicts=true. The application resolves by writing a merged revision and deleting the losing branch.

Worked example: the HTTP API

DB=http://admin:secret@localhost:5984

curl -X PUT $DB/notes                                    # create a database
curl -X PUT $DB/notes/n1 -H 'Content-Type: application/json' \
     -d '{"type":"note","title":"Ship log","tags":["sea"]}'
# {"ok":true,"id":"n1","rev":"1-..."}

curl $DB/notes/n1                                        # read, includes _rev
curl -X PUT $DB/notes/n1 -H 'Content-Type: application/json' \
     -d '{"_rev":"1-...","type":"note","title":"Ship log v2","tags":["sea"]}'

# Mango query with an index
curl -X POST $DB/notes/_index -H 'Content-Type: application/json' \
     -d '{"index":{"fields":["type","title"]},"name":"type-title"}'
curl -X POST $DB/notes/_find -H 'Content-Type: application/json' \
     -d '{"selector":{"type":"note","tags":{"$elemMatch":{"$eq":"sea"}}},"fields":["_id","title"]}'

# Continuous replication to another server (a POST to /_replicate is not
# persisted across restarts; for a durable job, write the same JSON as a
# document into the _replicator database instead)
curl -X POST $DB/_replicate -H 'Content-Type: application/json' \
     -d '{"source":"notes","target":"http://admin:secret@backup:5984/notes","continuous":true,"create_target":true}'

# Documents with conflicts
curl "$DB/notes/n1?conflicts=true"

A MapReduce view is stored in a design document. This one counts notes per tag; the built-in _count reducer is evaluated natively:

curl -X PUT $DB/notes/_design/tags -H 'Content-Type: application/json' -d '{
  "views": { "by_tag": {
    "map": "function (doc) { if (doc.type === \"note\") { doc.tags.forEach(function (t) { emit(t, 1); }); } }",
    "reduce": "_count"
  } } }'
curl "$DB/notes/_design/tags/_view/by_tag?group=true"

When to use it vs alternatives

NeedCouchDBAlternative
Offline-first sync to browsers and devicesCore strength (with PouchDB)Couchbase Mobile, Firebase
Ad hoc analytical queriesWeakMongoDB, PostgreSQL
Multi-document transactionsNot supportedMongoDB, relational databases
Simple ops, HTTP-only clientsStrong-

Pitfalls