CouchDB Mango Queries: When _find Beats Writing a Map/Reduce View
CouchDB's Mango query API lets you skip map/reduce views for ad-hoc lookups — but only if you back selectors with JSON indexes, paginate with bookmarks, and know where Mango's limits are.
24 Mar 2026, 18:50 UTC

You've got a CouchDB database full of order documents, and someone asks for "all orders over $500 placed last week, sorted by date." The traditional CouchDB answer is: write a JavaScript map function, maybe a reduce, publish a design document, wait for the view to build. For a one-off lookup or a fast-moving prototype, that's a lot of ceremony. Mango — CouchDB's declarative query API, available since version 2.0 — lets you post a JSON selector to /db/_find and get results immediately. The catch: convenience hides cost, and an unindexed Mango query is a full database scan. This post covers when Mango is the right call, how to keep it fast, and where it stops being enough.
What Mango actually is
Mango is a query layer (contributed from Cloudant's query service) that translates a JSON selector into an index lookup. You POST a selector to /_find:
POST /orders/_find
Content-Type: application/json
{
"selector": {
"type": "order",
"total": { "$gt": 500 },
"placed": { "$gte": "2026-10-04" }
},
"limit": 25
}
The selector language covers equality, ranges, and boolean combinations ($eq, $gt, $and, $or, and friends). Any HTTP client can run it — curl works fine, and no special permissions beyond database read access are needed for queries. Creating indexes requires database admin rights.
The index is not optional in production
If no index matches your selector, CouchDB scans every document in the database and returns results anyway — often with a warning field in the response saying no matching index was found. In a dev database with 200 documents, you won't notice. In production with two million, you will.
Create a JSON index with /_index:
POST /orders/_index
{
"index": { "fields": ["type", "placed", "total"] },
"name": "orders-by-date",
"type": "json"
}
Two rules matter here:
- Sorting must be index-backed. If your query includes a
sortarray, the index fields must match the sort order, or CouchDB rejects the query or warns about an in-memory sort. - Partial indexes keep things lean. If every query filters on a fixed value (like
type: "order"), add apartial_filter_selectorto the index definition. The index only contains matching documents, so it stays smaller and faster.
Before trusting a query, run it through POST /orders/_explain with the same body. The response tells you which index the planner picked — or that it picked none. That one check catches most Mango performance surprises before they ship.
Pagination: use bookmarks, not skip
Mango supports limit and skip, and skip looks like the obvious way to page. It isn't. Large skip values force CouchDB to read and discard rows, so page 500 costs roughly 500 pages of work.
Instead, each _find response includes a bookmark string. Pass it back in the next request to continue where you left off:
{
"selector": { "type": "order", "total": { "$gt": 500 } },
"sort": [{ "placed": "desc" }],
"limit": 25,
"bookmark": "<value from previous response>"
}
Treat bookmarks as opaque — don't construct, parse, or cache them across schema changes. Keep limit modest (tens, not thousands); large limits inflate per-request memory use. To verify pagination is correct, page through a test set until results are exhausted and confirm the total count matches a direct count, with no duplicates.
Where Mango stops and views begin
Mango is the right tool for ad-hoc lookups, admin tooling, and CRUD-style queries where the filter shape changes often. It is the wrong tool when you need:
- Aggregations — sums, counts, groupings. That's reduce functions in views.
- Joins across document types — CouchDB doesn't do them in either API; you denormalize or use view collation tricks.
- Full-text search — you need an external indexer or a search add-on.
- Guaranteed precomputed results — views build incrementally as documents change; Mango indexes also update, but complex view logic has no Mango equivalent.
Also note Mango requires CouchDB 2.0 or later — check with GET / and look at the version field if you're on an inherited deployment. Query planner behavior and default limits have shifted between releases, so read the release notes for the version you actually run.
A practical way to decide
Spin up a test database, load a few thousand representative documents, and run your selector twice: once before creating the JSON index, once after. Compare the warning field and response time, then confirm the plan with _explain. If the indexed query is fast and the selector shape is stable, Mango is a legitimate production choice — not just a prototyping shortcut. If you find yourself fighting the selector language to express the question, that's your signal to write the view instead.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.