CouchDB secondary indexes and views are fundamentally the same mechanism, just configured and accessed differently.

Let’s see them in action. Imagine we have a collection of users documents in CouchDB:

{"_id": "user1", "name": "Alice", "city": "New York", "type": "user"}
{"_id": "user2", "name": "Bob", "city": "London", "type": "user"}
{"_id": "user3", "name": "Charlie", "city": "New York", "type": "user"}

We want to find all users in "New York".

Using a View:

First, we define a view in a design document:

// design_doc_users
{
  "_id": "_design/users",
  "views": {
    "by_city": {
      "map": "function(doc) { if (doc.type === 'user') { emit(doc.city, doc.name); } }"
    }
  }
}

Then, we query it:

curl -X GET "http://localhost:5984/mydb/_design/users/_view/by_city?key=\"New York\""

This returns:

{
  "total_rows": 3,
  "offset": 0,
  "rows": [
    {
      "id": "user1",
      "key": "New York",
      "value": "Alice"
    },
    {
      "id": "user3",
      "key": "New York",
      "value": "Charlie"
    }
  ]
}

Using a Secondary Index (Search Index):

CouchDB’s built-in _find API uses a different indexing mechanism, often referred to as a "search index" or "secondary index" in this context. It’s not a separate feature but rather a different way to leverage CouchDB’s indexing capabilities.

To enable _find for our users documents, we don’t need to define a map function. Instead, we tell CouchDB which fields to index. This is done by POSTing an index definition to the _index endpoint:

curl -X POST "http://localhost:5984/mydb/_index" \
-H "Content-Type: application/json" \
-d '{
  "index": {
    "fields": ["city", "type"]
  },
  "ddoc": "user_indices",
  "name": "by_city_type"
}'

CouchDB will then build an index based on these fields. Once the index is ready (you can check its status), you can query it using the _find API:

curl -X POST "http://localhost:5984/mydb/_find" \
-H "Content-Type: application/json" \
-d '{
  "selector": {
    "city": "New York",
    "type": "user"
  }
}'

This returns a similar result:

{
  "docs": [
    {
      "_id": "user1",
      "_rev": "1-a1b2c3d4e5f678901234567890abcdef",
      "name": "Alice",
      "city": "New York",
      "type": "user"
    },
    {
      "_id": "user3",
      "_rev": "1-f0e9d8c7b6a543210fedcba987654321",
      "name": "Charlie",
      "city": "New York",
      "type": "user"
    }
  ],
  "bookmark": "...",
  "total_rows": 2
}

The Core Problem This Solves:

The fundamental problem both views and _find solve is efficient data retrieval. Without them, finding documents based on specific criteria would require scanning every single document in your database – a process that scales linearly with your data size. This is obviously unacceptable for any non-trivial dataset. By creating indexes, CouchDB can quickly locate relevant documents without reading everything.

How They Work Internally (The Mental Model):

At their heart, both views and the _find API rely on CouchDB’s indexing engine. When you define a view, CouchDB executes the map function for each document. The output of the map function (the key and value emitted) is then stored in a persistent, on-disk data structure (often a B-tree). When you query a view, CouchDB traverses this index to find matching keys.

The _find API, on the other hand, uses a more abstract indexing approach. When you define an index using _index, CouchDB builds a specialized index for the specified fields. The _find query then translates your selector into a query against these pre-built indexes. It’s essentially a more declarative and often simpler way to achieve similar results to views, especially for common filtering and sorting operations. The key difference is that _find indexes are managed by CouchDB itself based on your declarations, while views require you to explicitly define the map function logic.

Choosing the Right Approach:

  • Views: Offer maximum flexibility. You can perform complex transformations, group data, and emit arbitrary key-value pairs. They are excellent for aggregations, complex joins (by emitting related document IDs), and when you need precise control over the indexing logic. You can also define reduce functions for aggregation.
  • _find (Secondary Indexes): Simpler for common query patterns like filtering by field values, sorting, and basic range queries. They are generally easier to set up and manage for straightforward lookups. The _find API is also more performant for many common queries because CouchDB can optimize query execution against its internal index structures.

The One Thing Most People Don’t Know:

While views are often presented as the "older" way and _find as the "newer," the underlying indexing infrastructure is shared and continuously improved. A view’s map function and a _find index definition are both ways to tell CouchDB what data to index. The choice often comes down to the complexity of your query needs and your preference for explicit mapping logic versus declarative indexing. Furthermore, you can use both approaches within the same database; they don’t preclude each other.

The next concept to explore is how to leverage reduce functions with views for powerful aggregations.

Want structured learning?

Take the full Couchdb course →