The CouchDB per-user database pattern doesn’t actually store user data in a per-user database; it uses a single, shared database and filters access to individual users’ documents based on their UUID.

Let’s see this in action. Imagine we have a CouchDB instance and we want to give each user their own private space for their "notes."

First, we create a single database for all notes, let’s call it user_notes:

curl -X PUT http://localhost:5984/user_notes

Now, let’s say we have two users, Alice and Bob. Each user has a unique CouchDB UUID. We’ll represent them here as alice-uuid and bob-uuid.

Alice creates a note:

{
  "type": "note",
  "content": "My first secret note!",
  "owner_uuid": "alice-uuid"
}

We POST this to our user_notes database:

curl -X POST -H "Content-Type: application/json" -d '{"type": "note", "content": "My first secret note!", "owner_uuid": "alice-uuid"}' http://localhost:5984/user_notes

Bob creates his own note:

{
  "type": "note",
  "content": "Bob's brilliant idea.",
  "owner_uuid": "bob-uuid"
}
curl -X POST -H "Content-Type: application/json" -d '{"type": "note", "content": "Bob's brilliant idea.", "owner_uuid": "bob-uuid"}' http://localhost:5984/user_notes

Now, how do we make sure Alice only sees her notes and Bob only sees his? This is where a filter function comes in. A filter function is a JavaScript function that runs on the CouchDB server and determines which documents a user is allowed to see when they query a view.

We define a filter function named by_user in our user_notes database:

function(doc, req) {
  if (doc.owner_uuid === req.userCtx.uuid) {
    return true;
  }
  return false;
}

This filter function checks if the owner_uuid field of a document matches the UUID of the currently authenticated user (req.userCtx.uuid). If they match, it returns true, indicating the document should be included in the result. Otherwise, it returns false.

We register this filter function within CouchDB, typically by creating a design document. Let’s create one named _design/filters:

{
  "filters": {
    "by_user": "function(doc, req) { if (doc.owner_uuid === req.userCtx.uuid) { return true; } return false; }"
  }
}

And PUT it to our user_notes database:

curl -X PUT -H "Content-Type: application/json" -d '{"filters": {"by_user": "function(doc, req) { if (doc.owner_uuid === req.userCtx.uuid) { return true; } return false; }" } }' http://localhost:5984/user_notes/_design/filters

To make this useful, we need a view that returns documents by owner_uuid. Let’s create an index that emits owner_uuid and the document itself:

{
  "views": {
    "notes_by_owner": {
      "map": "function(doc) { if (doc.type === 'note') { emit(doc.owner_uuid, doc); } }"
    }
  }
}

And PUT this design document (let’s call it _design/notes) to our user_notes database:

curl -X PUT -H "Content-Type: application/json" -d '{"views": {"notes_by_owner": {"map": "function(doc) { if (doc.type === ''note'') { emit(doc.owner_uuid, doc); } }" } } }' http://localhost:5984/user_notes/_design/notes

Now, when Alice (authenticated as alice-uuid) queries the notes_by_owner view and applies the by_user filter, she will only see her notes.

Here’s how Alice would query it (assuming she’s authenticated):

curl "http://localhost:5984/user_notes/_design/notes/_view/notes_by_owner?include_docs=true&X-CouchDB-User-Id=alice-uuid&X-CouchDB-Authn=Basic%20dGhpcyBpcyBhIGZha2UgdG9rZW4%3D"
# In a real scenario, authentication headers would be used, not direct UUID in URL.
# The key is that req.userCtx.uuid will be 'alice-uuid' for this request.

The req.userCtx.uuid will be automatically populated by CouchDB if the user is authenticated. The filter function then ensures only documents where doc.owner_uuid matches alice-uuid are returned.

If Bob (authenticated as bob-uuid) makes the same query, he will only see his note.

curl "http://localhost:5984/user_notes/_design/notes/_view/notes_by_owner?include_docs=true&X-CouchDB-User-Id=bob-uuid&X-CouchDB-Authn=Basic%20dGhpcyBpcyBhIGZha2UgdG9rZW4%3D"
# Again, authentication is key here.

This pattern provides strong isolation because CouchDB’s security model is applied at the query level via the filter function. Even if another user somehow knew the _id of Alice’s document, they couldn’t retrieve it using this view because the filter would reject it.

The core concept is that the owner_uuid field acts as the "key" to a user’s data, and the filter function is the gatekeeper that only lets the rightful owner through. This avoids the overhead and complexity of managing thousands of individual databases, while still providing robust data privacy.

A common pitfall is forgetting to index the field used in the filter function (owner_uuid in this case), leading to extremely slow queries as CouchDB has to scan the entire database instead of using an index.

Want structured learning?

Take the full Couchdb course →