BrowserCouch Tutorial

This is a brief introduction to using the BrowserCouch API and the MapReduce mechanism. If you haven't already read it, you may want to check out the introduction to learn more about why this style of querying is being explored as an alternative to SQL for client-side Web Storage.

It should also be noted that BrowserCouch is by no means "mature" software. It currently lacks a lot of CouchDB's features that it ought to have, and its API is not stable at all.

Finally, a note about the code examples in this tutorial: they're actually being executed in your browser, and their output is sometimes being displayed in this tutorial too. While this helps ensure that the software is working as intended and also allows for some interactive learning opportunities, right now it also means that some parts of the code examples may look a bit unusual. Furthermore, if you see any conspicuously blank areas in this tutorial, it could be because the tutorial code crashed—our apologies if this occurs.

With that out of the way, let's get started.

Getting Started

Suppose we want to add offline support for a blog. To get a database called blog-posts in BrowserCouch, you can use the following function:

BrowserCouch.get('blog-posts', function onRetrieveCb(db) { blogDb = db; /* Save the DB for later. */ DONE(); }, new FakeStorage());

It's clear that the first parameter is the name of the database we want; the second parameter is the callback that will be passed the database once it's fetched.

The third parameter specifies the engine that will be used to persistently store our database across browsing sessions. In this case we're using FakeStorage, which just stores everything non-persistently in memory for the sake of example. We could just as easily leave out the third parameter to have BrowserCouch figure out the best storage backend based on our browser's capabilities.

If the database doesn't already exist, an empty one will be created for us. Putting blog posts into the database is done through the put() method like so:

blogDb.put( [{id: 0, author: 'Myk', title: 'Burritos', content: 'Burritos are yum.'}, {id: 1, author: 'Thunder', title: 'Bacon', content: 'I like bacon.'}, {id: 2, author: 'Thunder', title: 'Beer', content: 'Beer is good too.'}], function onDone() { /* Do stuff... */ DONE();} );

Every item we put into our database needs to have an id attribute, but aside from that, the item can contain any JSON-encodable data.

Views

Now that we've put some data into our database, we can play around with generating views on the data using the MapReduce mechanism. For instance, here's an ad-hoc view using only the map phase that organizes all the post titles by author:

blogDb.view({ map: function(doc, emit) { emit(doc.author, doc.title); }, finished: function(result) { displayInElement(result, 'author-keyed-view'); DONE(); } });

The view() method above has lots of optional arguments, which is why we're passing in a single object with keys corresponding to argument names. The map argument is the function to use for the map phase, and the finished argument is the callback to pass the view results into when processing is complete.

The output placed in the author-keyed-view element is:

As you can see, BrowserCouch essentially iterated over all of the blog posts, passing each one to map(), along with an arbitrary function called emit(). The map() function then emitted key-value pairs which show up in the view. It's worth noting that map() can call emit() as much as it wants to; each call will add a new row to the view.

At this point you may want to jump to the Try It For Yourself section to play around with making your own map() functions.

The reduce phase of a view is totally optional and a little confusing. Let's try adding a reduce() function to our earlier view to group together the blog post titles with the authors:

blogDb.view({ map: function(doc, emit) { emit(doc.author, doc.title); }, reduce: function(keys, values) { return values; }, finished: function(result) { authors = result; /* Save the result for later. */ displayInElement(authors, 'author-titles-view'); DONE(); } });

The output is as follows:

Essentially, BrowserCouch takes all the rows generated by map() and generates a new list of key-value rows, where the value of each row is the list of all values that match the row's key. This explains what the values argument passed to reduce() is.

The keys argument is a list of 2-tuples, the first of which is the key, and the second of which is the document id that emitted the key during the map phase.

The reduce() function is called for each unique key, and its return value is the value for its key in the final view.

Once you've got a view, you can use the view's findRow() method to find the first row whose key matches (or is closest to) the one you provide. For example:

var rowIndex = authors.findRow('Thunder'); displayInElement(authors.rows[rowIndex], 'author-find-row-view');

The output for this one is:

Try It For Yourself

If your eyes are crossed right now, no worries—most people take a long time to understand exactly what MapReduce is doing. That said, the easiest way to understand how MapReduce works is just to play around with creating your own view.

You can use the text field below to do just that. Just press the tab key when you're done making changes to recompute the view.

Here's the output to the above view:

Where To Go From Here

There's features in the API that aren't covered here, so check out the check out the test suite's annotated source code for more examples.