When I first started experimenting with CouchDB 0.10.0 and native Erlang views, I thought it would be a good idea to emit Erlang tuples directly from my view functions. It seemed natural Erlang is all about tuples, right, But the moment I tried it, CouchDB threw a confusing error. After digging deeper, I figured out why it happens and how to fix it. Let me walk you through what I learned, the error explained, and some extended functionality I built to practice.
My First Attempt Emitting Tuples
Here’s the first map function I wrote in Erlang:
%% Map Function
fun({Doc}) ->
case proplists:get_value(<<"type">>, Doc) of
<<"user">> ->
Identifiers = proplists:get_value(<<"identifiers">>, Doc),
ID = proplists:get_value(<<"_id">>, Doc),
Username = proplists:get_value(<<"username">>, Doc),
Name = proplists:get_value(<<"name">>, Doc),
lists:foreach(
fun(Identifier) ->
Emit(Identifier, {id, ID, username, Username, name, Name})
end,
Identifiers);
_ ->
ok
end
end.
I expected to see nicely structured results. Instead, CouchDB yelled at me.
The Error Explain
Here’s the exact error I got:
{"error":"json_encode","reason":"{bad_term,{<<\"user-1\">>,<<\"monkey\">>,<<\"Monkey Man\">>}}"}
So what’s going on?
- CouchDB requires all view results to be JSON-encoded.
- Erlang tuples (
{...}
) aren’t valid JSON. JSON only allows objects, arrays, strings, numbers, booleans, or null. - By writing
{id, ID, username, Username, name, Name}
, I created a pure Erlang tuple. CouchDB didn’t know how to turn that into JSON, so it crashed.
That explained why my earlier approach with lists like [ID, Username, Name]
worked fine—they map cleanly into JSON arrays.
The Fix: Use JSON Compatible Data
Instead of emitting tuples, I had to structure my output in a way CouchDB could convert into JSON. The cleanest way is to use proplists (property lists), which CouchDB can turn into JSON objects.
Here’s the fixed version:
lists:foreach(
fun(Identifier) ->
Emit(Identifier, {[{<<"id">>, ID},
{<<"username">>, Username},
{<<"name">>, Name}]})
end,
Identifiers).
Now the output looks like this:
{
"id": "user-1",
"key": "ABC",
"value": {
"id": "user-1",
"username": "monkey",
"name": "Monkey Man"
}
}
Perfect exactly the structured JSON I wanted.
Extra Practice Functionality
Once I fixed the basic problem, I decided to stretch the project further by adding a couple of practice improvements.
Emit Both Identifier and Username as the Key
Instead of just using the identifier, I combined it with the username. This way I could query by both at once:
lists:foreach(
fun(Identifier) ->
Emit([Identifier, Username],
{[{<<"id">>, ID},
{<<"username">>, Username},
{<<"name">>, Name}]})
end,
Identifiers).
Now my view results look like this:
{
"key": ["ABC", "monkey"],
"value": {
"id": "user-1",
"username": "monkey",
"name": "Monkey Man"
}
}
This compound key gives me more flexibility in how I query and filter results.
Emit a Count of Identifiers
I also wanted to see how many identifiers each user had. For that, I wrote a simple count-emitting view:
fun({Doc}) ->
case proplists:get_value(<<"type">>, Doc) of
<<"user">> ->
Identifiers = proplists:get_value(<<"identifiers">>, Doc),
Emit(<<"identifier_count">>, length(Identifiers));
_ ->
ok
end
end.
Using CouchDB’s built-in _sum
reducer, I can now aggregate across all users and quickly calculate how many identifiers exist in total.
Final Thought
This little project taught me an important lesson just because Erlang loves tuples doesn’t mean CouchDB does too. CouchDB’s view engine only knows how to serialize JSON-compatible types, so sticking with lists, binaries, or proplists is the right way to go. Once I made that change, everything worked as expected and I even extended the code to create compound keys and identifier counts for practice.