NoSQL Document Stores: MongoDB and CouchDB Concepts

Document-oriented databases store data as self-contained documents, typically in JSON or BSON format. Unlike relational databases that spread data across multiple tables with foreign keys, document...

Key Insights

  • Document stores excel at handling semi-structured data with flexible schemas, making them ideal for content management, user profiles, and applications where data models evolve frequently.
  • MongoDB prioritizes query flexibility and strong consistency with its rich query language and replica sets, while CouchDB focuses on eventual consistency, HTTP-based access, and robust offline-first synchronization.
  • Choose MongoDB when you need complex queries and immediate consistency; choose CouchDB when you need reliable replication, offline capabilities, and a simpler HTTP API.

Introduction to Document Stores

Document-oriented databases store data as self-contained documents, typically in JSON or BSON format. Unlike relational databases that spread data across multiple tables with foreign keys, document stores keep related information together in a single, hierarchical structure.

The fundamental difference lies in schema flexibility. Relational databases enforce a rigid schema—every row in a table must conform to the same structure. Document stores allow each document to have different fields, enabling you to evolve your data model without migrations.

Consider a user profile in a relational database versus a document store:

-- Relational approach: multiple tables
CREATE TABLE users (
    id INT PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(100)
);

CREATE TABLE user_addresses (
    id INT PRIMARY KEY,
    user_id INT,
    street VARCHAR(100),
    city VARCHAR(50),
    FOREIGN KEY (user_id) REFERENCES users(id)
);

CREATE TABLE user_preferences (
    user_id INT PRIMARY KEY,
    theme VARCHAR(20),
    notifications BOOLEAN,
    FOREIGN KEY (user_id) REFERENCES users(id)
);
// Document store approach: single document
{
  "_id": "user123",
  "username": "johndoe",
  "email": "john@example.com",
  "addresses": [
    {
      "street": "123 Main St",
      "city": "Portland"
    }
  ],
  "preferences": {
    "theme": "dark",
    "notifications": true
  }
}

Choose document stores when you need schema flexibility, have hierarchical data, require horizontal scalability, or build applications where data access patterns favor retrieving entire documents rather than joining multiple tables.

MongoDB Architecture and Core Concepts

MongoDB stores documents in collections (analogous to tables) using BSON, a binary JSON format that supports additional data types like dates and binary data. Each document gets a unique _id field automatically.

Basic CRUD operations in MongoDB use a straightforward API:

// Create
db.products.insertOne({
  name: "Laptop",
  price: 999.99,
  specs: {
    cpu: "Intel i7",
    ram: "16GB"
  },
  tags: ["electronics", "computers"]
});

// Read
db.products.findOne({ name: "Laptop" });

// Query with conditions
db.products.find({ 
  price: { $gte: 500, $lte: 1500 },
  "specs.ram": "16GB"
});

// Update
db.products.updateOne(
  { name: "Laptop" },
  { $set: { price: 899.99 }, $push: { tags: "sale" } }
);

// Delete
db.products.deleteOne({ name: "Laptop" });

Indexing is critical for query performance. MongoDB supports various index types:

// Single field index
db.products.createIndex({ name: 1 });

// Compound index
db.products.createIndex({ category: 1, price: -1 });

// Text index for full-text search
db.products.createIndex({ description: "text" });

// Geospatial index
db.locations.createIndex({ coordinates: "2dsphere" });

MongoDB’s aggregation pipeline enables complex data transformations:

db.orders.aggregate([
  { $match: { status: "completed" } },
  { $group: {
      _id: "$customerId",
      totalSpent: { $sum: "$amount" },
      orderCount: { $sum: 1 }
  }},
  { $sort: { totalSpent: -1 } },
  { $limit: 10 }
]);

MongoDB uses replica sets for high availability—one primary node handles writes while secondary nodes replicate data. For horizontal scaling, sharding distributes data across multiple servers based on a shard key.

CouchDB Architecture and Core Concepts

CouchDB takes a different approach, emphasizing HTTP-based access and eventual consistency. Every operation uses standard HTTP methods, making it accessible from any HTTP client.

# Create a document (POST generates ID)
curl -X POST http://localhost:5984/products \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Laptop",
    "price": 999.99,
    "specs": {
      "cpu": "Intel i7",
      "ram": "16GB"
    }
  }'

# Read a document
curl -X GET http://localhost:5984/products/doc_id

# Update (requires current revision)
curl -X PUT http://localhost:5984/products/doc_id \
  -H "Content-Type: application/json" \
  -d '{
    "_rev": "1-abc123",
    "name": "Laptop",
    "price": 899.99
  }'

# Delete (also requires revision)
curl -X DELETE http://localhost:5984/products/doc_id?rev=2-def456

CouchDB uses Multi-Version Concurrency Control (MVCC). Every document update creates a new revision, preventing write conflicts. The _rev field tracks document versions.

Instead of real-time queries, CouchDB uses MapReduce views—pre-computed indexes defined in JavaScript:

// Design document with a view
{
  "_id": "_design/products",
  "views": {
    "by_price": {
      "map": "function(doc) { if (doc.price) { emit(doc.price, doc.name); } }"
    },
    "total_by_category": {
      "map": "function(doc) { if (doc.category) { emit(doc.category, doc.price); } }",
      "reduce": "_sum"
    }
  }
}

// Query the view
curl -X GET http://localhost:5984/products/_design/products/_view/by_price?startkey=500&endkey=1500

Data Modeling Patterns

Document stores require different modeling strategies than relational databases. The key decision is whether to embed related data or reference it.

Embedding works well for one-to-few relationships:

// MongoDB: Embedded approach
{
  "_id": "order123",
  "customerId": "cust456",
  "items": [
    { "productId": "prod789", "quantity": 2, "price": 29.99 },
    { "productId": "prod012", "quantity": 1, "price": 49.99 }
  ],
  "total": 109.97
}

Referencing suits one-to-many or many-to-many relationships:

// MongoDB: Referenced approach
// Order document
{
  "_id": "order123",
  "customerId": "cust456",
  "itemIds": ["item789", "item012"],
  "total": 109.97
}

// Separate item documents
{
  "_id": "item789",
  "orderId": "order123",
  "productId": "prod789",
  "quantity": 2,
  "price": 29.99
}

Denormalization is common in document stores. Duplicate data to optimize read performance:

// Denormalized product info in order
{
  "_id": "order123",
  "items": [
    {
      "productId": "prod789",
      "productName": "Widget",  // Denormalized
      "quantity": 2,
      "price": 29.99
    }
  ]
}

The polymorphic pattern handles varying document structures in the same collection:

// Different event types in same collection
{ "type": "page_view", "url": "/home", "timestamp": "2024-01-15" }
{ "type": "purchase", "amount": 99.99, "items": [...], "timestamp": "2024-01-15" }
{ "type": "signup", "email": "user@example.com", "timestamp": "2024-01-15" }

Querying and Performance Comparison

MongoDB’s query language offers immediate, flexible queries:

// Complex MongoDB query
db.products.find({
  $and: [
    { price: { $gte: 100 } },
    { $or: [
      { category: "electronics" },
      { tags: { $in: ["featured", "sale"] } }
    ]}
  ]
}).sort({ price: -1 }).limit(20);

CouchDB requires pre-defining views, which are incrementally updated:

// Equivalent CouchDB view
{
  "map": "function(doc) {
    if (doc.price >= 100 && 
        (doc.category === 'electronics' || 
         (doc.tags && (doc.tags.indexOf('featured') >= 0 || 
                       doc.tags.indexOf('sale') >= 0)))) {
      emit(doc.price, doc);
    }
  }"
}

MongoDB excels at ad-hoc queries but requires careful indexing. CouchDB’s views are slower to build but blazing fast to query once computed. For write-heavy workloads, CouchDB’s append-only architecture can outperform MongoDB.

Replication and Consistency Models

MongoDB replica sets provide strong consistency. The primary node handles all writes, and you can configure read preferences:

// MongoDB replica set configuration
rs.initiate({
  _id: "myReplicaSet",
  members: [
    { _id: 0, host: "mongo1:27017" },
    { _id: 1, host: "mongo2:27017" },
    { _id: 2, host: "mongo3:27017" }
  ]
});

// Read from secondaries (eventual consistency)
db.products.find().readPref("secondary");

CouchDB embraces eventual consistency with master-master replication:

# Set up continuous replication
curl -X POST http://localhost:5984/_replicate \
  -H "Content-Type: application/json" \
  -d '{
    "source": "http://server1:5984/mydb",
    "target": "http://server2:5984/mydb",
    "continuous": true
  }'

Under the CAP theorem, MongoDB prioritizes consistency and partition tolerance (CP), while CouchDB favors availability and partition tolerance (AP). This makes CouchDB excellent for offline-first applications where nodes sync when connectivity resumes.

Choosing Between MongoDB and CouchDB

Use MongoDB when you need:

  • Complex queries and aggregations
  • Strong consistency guarantees
  • Rich ecosystem with extensive tooling
  • Transactions across multiple documents
  • Geospatial queries or full-text search

Use CouchDB when you need:

  • Offline-first applications with sync
  • Master-master replication
  • Simple HTTP API without drivers
  • Append-only storage for audit trails
  • Built-in conflict resolution

MongoDB has wider adoption, more extensive documentation, and better third-party integration. CouchDB shines in distributed scenarios where nodes operate independently and sync periodically.

For most applications requiring a document store, MongoDB’s query flexibility and ecosystem make it the safer choice. Choose CouchDB when its replication model solves a specific architectural challenge, particularly in mobile or edge computing scenarios.

Both databases continue evolving—MongoDB added multi-document transactions, while CouchDB improved query capabilities with Mango queries. Evaluate your specific requirements around consistency, query patterns, and deployment architecture to make the right choice.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.