Blog Post

Node.js ODM for Couchbase (Ottoman)

Brett Lawson of Couchbase Published

A few months back, I was talking with some of the users of our Node.js client and heard a fairly standard opinion that people were looking for some method to simplify their life further when using Couchbase from Node.js.  One particular things that I heard a lot about was they were looking for some way to have automatically generated models to avoid needing to manually build up all the boilerplate themselves.

In response to this, I started working on a new library known as Ottoman.  Ottoman is an experimental ODM designed around Couchbase.   At this point I feel that Ottoman is sufficiently feature rich to begin having some external eyes taking a look.  I know that there are bugs hiding out in the source, and features that could be enhanced but I'd like your help to do it!  You're the one with the best idea of what you want to see and what would be most beneficial to YOU, so I want to hear from you with as many of the questions, comments and/or concerns you might have.

Onwards with some explanations! 

The library is meant to allow you to build a definition of what your model would look like, and then have it auto-generate all the boilerplate that usually goes with this.  As an example, take a look at the following:

var User = Ottoman.model('User', {
  'username': 'string',
  'name': 'string',
  'email': 'string'
}, {
  bucket: new couchbase.Connection({})
});

This example shows how to create a simple User model, which contains 5 fields (yes 5, I will explain this below).  It intuitively contains a 3 string fields, which are username, name and email.  Additionally, there are 2 `hidden` fields which are added on and which can be overridden.  These fields are the _type and _id fields, which contain a string of what model this document is, as well as a uniquely identifying string, which defaults to using a UUIDv4 uuid.

Using this newly created model is extremely simple as the return object from the model function is a function that can be used to instantiate new instances of the model.  So following the example above, we can create and save a new user with the following:

var test = new User();
test.username = 'brett19';
test.name = 'Brett Lawson';
test.email = 'brett19@gmail.com';

Ottoman.save(test, function(err) {
  if (err) throw err;
  console.log('saved');
});

Ottoman additionally allows you to quickly load an object that was previously stored to the database by using findById as well, such as this:

User.findById(test._id, function(err, obj) {
  if (err) throw err;

  console.log(obj.name);
  // Brett Lawson
});

So far, we have essentially just looked at some simple load/store operations that Ottoman can do, lets get a bit deeper than that and see where Ottoman can really help increase our productivity.  Lets now say that we are building a blog site, and we need to store a blog post associated to users.  Lets build a model to let us do this.

var BlogPost = Ottoman.model('User', {
  'creator': 'User',
  'title': 'string',
  'content': 'string'
}, {
  bucket: ...
});

You can see here that one of our field types actually refers to our User object created above.  This allows us to create a new BlogPost that refers to another document stored in our database.  You can also configure a model to be embedded.  Lets say we wanted to store the current GPS location of the user, as well as their GPS location when they make a post.  We could define a model like this, and use it from User and BlogPost, rather than being a normal referenced document, the data will be directly embedded in their respective objects, but remain as GPSPosition's when de-serialized.

var GPSPosition = Ottoman.model('GPSPosition', {
  lat: 'number',
  long: 'number'
}, {
  embed: true
});

The last interesting feature I would like to mention today is the ability to automatically generate map/reduce views that allow you to preform basic lookups for our models.  I would like to strongly prefix this by saying this feature is very much experimental and only works for some of the most common use cases.  Additionally it has the caveat of needing you go manually call `registerDesignDocs` on the Ottoman object after all your models have been registered as this is the only way to determine model linkages, additionally, regenerating these views on every startup of your app would be horribly inefficient, so I usually do it as a sort of `setup phase` at the moment.

To take our blog example even further, lets say we needed a list of our user's blog posts.  Normally this would involve manually building a view to extract this list by searching all BlogPost's on the creator reference for the specific user we are looking for.   With Ottoman this is much simpler.   We can simply define a query in the properties for our User object like so:

      queries: {
        myPosts: {
          target: 'BlogPost',
          mappedBy: 'creator',
          sort: 'desc',
          limit: 5
        }
      },

Now you can request the posts made by a user simply by invoking the myPosts method on any User instance and you will be returned a list of BlogPost objects that were posted by this user, the location and deserialization of these objects is handled automatically.

There are a ton more features supported by Ottoman that I don't have time to show off today, but if you want to see more or want to help out make Ottoman even better, head on over to the GitHub page here: https://github.com/couchbaselabs/node-ottoman/ !

Cheers! Brett