Get CouchDB Running in 5 Minutes — Do First, Ask Questions Later
I won’t start with theory. Let’s get it installed, run it, and then you’ll understand why it’s different.
Installing on Ubuntu/Debian
# Add the official Apache CouchDB GPG key and repository
curl -L https://couchdb.apache.org/repo/keys.asc | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/couchdb.gpg
echo "deb https://apache.jfrog.io/artifactory/couchdb-deb/ $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/couchdb.list
sudo apt update && sudo apt install -y couchdb
The setup wizard will ask for a mode: choose standalone for a single node, or clustered for multiple machines. Set your admin password here — you won’t get another chance.
Verify the service is up:
sudo systemctl status couchdb
curl http://localhost:5984/
# Expected output:
# {"couchdb":"Welcome","version":"3.x.x", ...}
Creating Your First Database and Document
# Create a database named "myapp"
curl -X PUT http://admin:yourpassword@localhost:5984/myapp
# Add the first JSON document
curl -X POST http://admin:yourpassword@localhost:5984/myapp \
-H "Content-Type: application/json" \
-d '{"name": "Nguyen Van A", "role": "developer", "city": "Hanoi"}'
# View all documents in the database
curl http://admin:yourpassword@localhost:5984/myapp/_all_docs?include_docs=true
Visit http://localhost:5984/_utils to access Fauxton — the built-in Web UI, no extra installation needed. Creating, viewing, and editing documents manually here is fast and convenient during development.
I’ve Used MySQL, PostgreSQL, and MongoDB — What Problem Does CouchDB Actually Solve?
Having worked with all three across multiple projects, I’ve found each has its place. MySQL/PostgreSQL excel when data has a fixed structure and clear relationships. MongoDB offers more schema flexibility, but its replication leans toward primary-secondary — secondary nodes are read-only, not writable.
CouchDB solves a fundamentally different problem: apps that need to work without a network connection, then sync back up when connectivity returns. Field worker apps, POS systems in low-signal areas, logistics systems in remote locations — this is CouchDB’s home turf.
Three Ways CouchDB Is Genuinely Different
- Pure HTTP API: Every operation goes through REST. No special driver needed —
curlis all you need to get started. - MVCC (Multi-Version Concurrency Control): No write locks. Each document has a
_revfield tracking its revision. Reads are never blocked by writes, giving you excellent read throughput. - Multi-master replication: Every node accepts writes. When syncing, CouchDB automatically detects conflicts — you decide how to resolve them, not the database.
CouchDB’s Document Model
Each document is JSON with an _id (auto-generated or custom) and a _rev (revision hash):
{
"_id": "order_20260710_001",
"_rev": "1-abc123def456",
"customer": "Tran Thi B",
"items": [
{"sku": "LAP001", "qty": 2, "price": 15000000},
{"sku": "MOU002", "qty": 1, "price": 350000}
],
"total": 30350000,
"status": "pending",
"created_at": "2026-07-10T09:00:00Z"
}
Every update changes the _rev. If two nodes edit the same document while offline, CouchDB keeps both versions and marks the document as conflicted — you decide how to merge later.
Configuring Replication — Where CouchDB Truly Shines
One-Way Replication
# Replicate the "myapp" database from server A to server B
curl -X POST http://admin:pass@localhost:5984/_replicate \
-H "Content-Type: application/json" \
-d '{
"source": "http://admin:pass@server-a:5984/myapp",
"target": "http://admin:pass@server-b:5984/myapp",
"create_target": true
}'
Continuous Replication
# "continuous": true keeps replication running indefinitely, not just once
curl -X POST http://admin:pass@localhost:5984/_replicate \
-H "Content-Type: application/json" \
-d '{
"source": "http://admin:pass@server-a:5984/myapp",
"target": "http://admin:pass@server-b:5984/myapp",
"continuous": true,
"create_target": true
}'
Bidirectional Replication
Run two jobs in parallel — one direction A→B, one direction B→A:
# Direction A → B
curl -X POST http://admin:pass@server-a:5984/_replicate \
-H "Content-Type: application/json" \
-d '{"source": "myapp", "target": "http://admin:pass@server-b:5984/myapp", "continuous": true}'
# Direction B → A
curl -X POST http://admin:pass@server-b:5984/_replicate \
-H "Content-Type: application/json" \
-d '{"source": "myapp", "target": "http://admin:pass@server-a:5984/myapp", "continuous": true}'
Once configured, both servers sync automatically whenever changes occur — even when one side has just come back online after a period of downtime. No additional setup needed.
Handling Conflicts After Sync
Conflicts happen more often than you’d expect — especially when field workers use the app on spotty 3G connections, go offline for a couple of hours, then sync back. CouchDB doesn’t auto-merge content — it lets you choose which revision wins.
import requests
base = "http://admin:yourpassword@localhost:5984"
# Find documents with conflicts
def find_conflicts(db):
url = f"{base}/{db}/_all_docs?include_docs=true&conflicts=true"
docs = requests.get(url).json()["rows"]
return [row["doc"] for row in docs if "_conflicts" in row.get("doc", {})]
# Get the conflicting revisions
def get_conflict_revs(db, doc_id, conflict_revs):
results = []
for rev in conflict_revs:
url = f"{base}/{db}/{doc_id}?rev={rev}"
results.append(requests.get(url).json())
return results
# Delete the losing revision (keep the one you want)
def resolve_conflict(db, doc_id, losing_rev):
url = f"{base}/{db}/{doc_id}?rev={losing_rev}"
resp = requests.delete(url)
print(f"Deleted losing rev: {resp.status_code}")
# Example usage
conflicts = find_conflicts("myapp")
for doc in conflicts:
print(f"Conflict on: {doc['_id']}")
revs = get_conflict_revs("myapp", doc["_id"], doc["_conflicts"])
# Keep the current revision (winning), delete the conflicting ones
for rev in doc["_conflicts"]:
resolve_conflict("myapp", doc["_id"], rev)
Merge logic depends on your business rules: for orders you might take the latest by created_at, for activity logs you might want to keep both. CouchDB provides the mechanism — you write the rules.
4 Things to Know Before Using CouchDB in Production
1. Pair It with PouchDB on the Client — a Complete Offline-First Stack
PouchDB is a JavaScript library (~46KB gzipped) that runs directly in the browser or React Native. Its API mirrors CouchDB’s, and it automatically syncs both ways when connectivity is restored:
// In your web/React Native app
const db = new PouchDB('local_myapp');
const remoteDB = new PouchDB('http://admin:pass@yourserver:5984/myapp');
// Enable bidirectional sync, auto-reconnects when network is restored
db.sync(remoteDB, { live: true, retry: true })
.on('change', info => console.log('Synced:', info))
.on('error', err => console.error('Sync error:', err));
Users can enter data offline without any disruption. When the network returns, PouchDB automatically pushes and pulls changes — no extra code required.
2. Create Indexes for Faster Queries
By default, CouchDB uses views (MapReduce) — verbose to write. Since CouchDB 2.x, Mango Query is available and much easier:
# Create an index on the "status" and "created_at" fields
curl -X POST http://admin:pass@localhost:5984/myapp/_index \
-H "Content-Type: application/json" \
-d '{"index": {"fields": ["status", "created_at"]}, "name": "status-index"}'
# Query using Mango syntax
curl -X POST http://admin:pass@localhost:5984/myapp/_find \
-H "Content-Type: application/json" \
-d '{
"selector": {"status": "pending"},
"sort": [{"created_at": "desc"}],
"limit": 20
}'
3. When NOT to Use CouchDB
- Need complex JOINs across multiple tables → use PostgreSQL.
- Time-series data, metrics → use TimescaleDB or InfluxDB.
- High-speed caching → use Redis.
- CouchDB is the right choice when: data is document-shaped, you need offline-first functionality, and you want multi-master without dealing with distributed locks.
4. Enable CORS When Using PouchDB from the Browser
curl -X PUT http://admin:pass@localhost:5984/_node/nonode@nohost/_config/httpd/enable_cors \
-d '"true"'
curl -X PUT http://admin:pass@localhost:5984/_node/nonode@nohost/_config/cors/origins \
-d '"*"'
curl -X PUT http://admin:pass@localhost:5984/_node/nonode@nohost/_config/cors/methods \
-d '"GET,PUT,POST,HEAD,DELETE"'
Replace "*" with your specific domain in production. Using "*" during development is fine, but don’t leave it that way when you deploy.
CouchDB isn’t the right database for every problem. But when your app needs to run without a network and sync back, or when multiple nodes need to write simultaneously without the headache of distributed locking — it handles exactly that, no workarounds needed.

