Doing Cool Stuff w/ CouchDB  January 6th, 2009

I've been looking for an excuse to use CouchDB for something for quite a while now. There's been lots of controversy around it and it's only getting louder the more popular/mainstream the project gets.

One of the biggest points for/against CouchDB is that it definitely is not a replacement to a regular RDBMS (like MySQL) that we're all used to. For in the sense that sometimes MySQL is overkill. Against in the sense that it's got a tiny fraction of MySQL's features.

Nichey vs Overkill

The problem is that CouchDB seems, to me, to have a very specific niche. It took me forever to find a project that it would be better at than something 'normal' like MySQL. Finally, I decided to add a 'ratings' feature to this blog, not an uncommon thing, using CouchDB.

This entire site (except for a few small parts, stay tuned for future posts!) is pre-generated by a system not unlike (but quite a bit more elaborate than) what I discussed in Doing Cool Stuff w/ CouchDB. Now, if you ask me, it would be crazy to install MySQL (or whatever), setup a Rails app or something and integrating my static content into it somehow (either in the new db or...), blah, blah, blah — just to get a rating system.

Instead, I thought, why don't I just throw documents at it with some basic information, and use CouchDB's RESTfulness to give jQuery what it needs to allow the user to do the rating... Cool!

Back-end w/ CouchDB

First I needed to setup/install CouchDB. Easy enough on my Ubuntu server. Then I created a database with a design document that looks like this:

So, documents (our ratings) can be in any form that includes a permalink attribute, a rating attribute and a type attribute with a value of "rating" (we could use other types in the future for other, unrelated stuff).

If you aren't familiar with how CouchDB views work, you might want to check out their (quite good) documentation on the subject before going on.

The by_permalink view includes both a map and a reduce which means that (when called with group=true) this view will return data like:

Notice that it's just plain old JSON. You know what's good at dicing JSON? That's right. Everything.

Roadblock

There's a problem coming up here, though. In order to get these results we call a URL like http://localhost:5984/rf/_view/ratings/by_permalink?group=true, yes, localhost. We could open up the server to external requests but then what's to stop someone from just inserting any document, deleting any document (or database!) they like? Well, it's much simpler to just only expose the server to the machine it's actually running on. This means that we will need some backmiddle-end, afterall.

Middle-end w/ Sinatra

Sinatra is totally awesome... Following the (quite good!) Sinatra (and Rack, hint) documentation, I was able to setup Sinatra in front of my entire site and just intercept calls to /api/* as requests Sinatra should actually attempt to handle. The rest pass through to my static stuff. Here's what the code might look like:

So far so good. You should now be able to play around with the database a little just using those two actions. With this Sinatra app running, you might write a script like this:

...and running it like:

Now that we have Sinatra as middle-ware-of-sorts for our database we need to use this stuff on the frontend part.

Front-end w/ jQuery

Normally, for this sort of thing, I write it up as a jQuery plugin and include it on the pages I'll be using it on. Then I'll add it to my site's global $(document).ready with something along the lines of...

...thus keeping your site-wide onload quite clean, and that's exactly what I've done for ryanfunduk.com, but you could go lots of ways to accomplish this. Take a look at the code I use on this site or the version written inline in this demo of the code in this article. Sorry this blog has been redone and that stuff isn't there anymore!

Improvements

As usual there are some enhancements you can make to this. One obvious one is that since CouchDB handles document revisions for you more-or-less automatically we can append something unique to the _id of the document (say, the user's IP address). This way a user rating an article more than once will just update their previously created document (assuming their IP doesn't change, of course).

Other things you could do: Create another Sinatra action and another CouchDB view giving a breakdown of all the ratings that have been made — Perhaps with graphs by date (remember you can just add arbitrary data to documents, why not timestamps?). Or maybe some administration features so you can moderate the system.

Some social stuff: