Filtered replication in CouchDB is surprisingly powerful because it lets you copy specific subsets of documents, not just whole databases, between servers.
Let’s see it in action. Imagine you have a products database on a central server, and you want a mobile client to only get products tagged as "on_sale."
First, on the central server (let’s say it’s http://admin:password@localhost:5984), we set up a filter function in the _design/app document of our products database. This filter will run on every document change.
// _design/app in products database
{
"_id": "_design/app",
"filters": {
"on_sale_filter": "function(doc, req) { return doc.on_sale === true; }"
}
}
This JavaScript function on_sale_filter receives each document (doc) and the replication request (req). If the document has on_sale set to true, it returns true, telling CouchDB to include this document in the replication. Otherwise, it returns false.
Now, on the client (let’s say it’s http://user:pass@mobile-couch:5984/products_local), we initiate a replication from the central server to the local one, specifying our filter.
curl -X POST \
http://user:pass@mobile-couch:5984/products_local/_replicate \
-H 'Content-Type: application/json' \
-d '{
"source": "http://admin:password@localhost:5984/products",
"target": "products_local",
"filter": "app/on_sale_filter",
"create_target": true,
"continuous": true
}'
Here’s the breakdown:
source: The remote database we’re replicating from.target: The local database we’re replicating to.create_target: trueensures it’s created if it doesn’t exist.filter: This is the magic. It’s in the formatdesign_doc_name/filter_function_name. So,app/on_sale_filtertells CouchDB to use theon_sale_filterfunction defined within the_design/appdocument of thesourcedatabase.continuous: true: Keeps the replication going, so new "on sale" products are automatically sent to the client.
When this replication starts, CouchDB on the source server will iterate through all documents in products. For each document, it executes the on_sale_filter function. Only documents for which the filter returns true are sent to the products_local database on the mobile client. If you add a new product and set on_sale to true, the continuous replication will pick it up and send it over.
The real power here is that you can pass parameters to your filter functions. Imagine a scenario where you want to replicate only products from a specific category for a particular sales rep.
On the central server, you’d modify the filter:
// _design/app in products database
{
"_id": "_design/app",
"filters": {
"category_filter": "function(doc, req) { if (doc.category === req.query.category) { return true; } return false; }"
}
}
And then trigger the replication with a query parameter:
curl -X POST \
http://user:pass@mobile-couch:5984/products_local \
-H 'Content-Type: application/json' \
-d '{
"source": "http://admin:password@localhost:5984/products",
"target": "products_local",
"filter": "app/category_filter",
"query_params": {
"category": "electronics"
},
"create_target": true,
"continuous": true
}'
Now, the req.query.category inside the filter function will be "electronics". Only documents where doc.category matches "electronics" will be replicated. This allows for dynamic, client-driven filtering without needing separate design documents for each filtering criteria.
The most counterintuitive aspect of CouchDB filters is that they operate on the source server during replication. This means the filtering logic and the data that is not filtered out are processed on the source, not the target. This can be a performance consideration if your filters are complex or your source database is under heavy load. It also means that if you’re replicating from a filtered view, the filter function itself is executed on the source database for every document change, not just on the initial replication.
The next step is understanding how to handle conflicting document versions when using filtered replication, especially in multi-master setups.