Couchbase + Nodejs = Rapid Development
Last year the owner of all things nodejs at Couchbase, Brett Lawson, debuted a preview of our upcomming ODM (Object Data Mapper) for Node.js referred to as “Ottoman.” Since that time, it has consistently been one of the most requested items from Couchbase node developers–“when can we have Ottoman?” The time has come! Wiith Ottoman now officially released, I'm happy to present a walkthrough of building a sample nodejs application and how using Couchbase and Ottoman facilitates a powerful rapid development experience, especially when building REST API's and prototyping complex data models.
Why is having an ODM so desirable–there's already a really mature Node.js client for Couchbase? To understand the answer to this question, lets look at a real world example of an inventory application for a motorcycle dealership built using Node.js and Couchbase.
Ride more, code less
- An ODM allows the developer to:
- Work natively in objects. Starting in nodejs v0.12 this is going to become increasingly important
- Rapidly prototype and define your data model including relationships
- Let someone else handle the heavy lifting and tedious tasks.
- Support many different datatypes, several of which are predefined in Ottoman
- Support custom datatypes, including custom validators
- Model your data for embedding objects or references, and keep those relationshps crisp.
- Provides support for generic finds
- Supports multiple Indexing strategies, natively
- Control and enforce the data model from within the application.
- Programitically define object methods that map to the data model.
Building a Node.js Application Using an ODM and Couchbase
For reference, the sample code is available on github There are three easy steps to getting started with building node applications with Couchbase. Let's get those out of the way before we go any futher:
Download and install CB 4.0 Enterprise. The enterprise version of the product is absolutely free to use in any development capacity. The latest version can be downloaded from our website. After installing, open your browser to port 8091 where Couchbase is installed, and the graphical UI will walk through the setup process. For the purpose of this walkthrough and application, we’re going to assume Couchbase is installed on the same OS the application will be developed on. To connect to the instance of Couchbase that was installed, open a browser to http://127.0.0.1:8091. Be sure to select all thee services Data, Index, Query on the “configure server” page under “Start a new Cluster”. We’ll need these for building our sample application.
We don’t need to use one of the included sample buckets with this tutorial. Click next on the page that says “Sample Buckets” without selecting any. Continue through the setup, adding a default bucket and now we should be ready to continue on with building our application
Install nodejs from the node website. Once nodejs is installed we can bootstrap our application. In order to bootstrap the application, create a directory and clone the repository on github. From the terminal
git clone https://github.com/ToddGreenstein/bikeshop-cb.git ~/bikeshop-cb/
The package.json file in the root directory of the application lists the dependencies for the application. Ottoman is currently listed as the beta branch that is stored in github, while the latest version of the other dependencies will be installed.
The next step to install the dependencies. From the terminal run
The requirements for this application are:
- Inventory Application for Cataloguing bikes.
- The Application must store Customer and Salesperson information to track test rides and purchase information
- Couchbase will be the system of record.
- The application needs to be decoupled from front end frameworks, and support integration to other backend systems through HTTP REST API.
The flexiblity and dynamic nature of a NOSQL Document Database and JSON simplifies building the data model. For the bikeshop application we will use three types of objects, and we'll define those in specific modules in the node application.
A folder under the root of the application called /schema is where the data model is defined, and in /schema/model separate model files for each object is defined there. Lets walk through the data model starting with the customer.js module.
The first section of the customer.js module instantiates module dependencies, which are Ottoman and the database file where the information on the Couchbase instance is stored for this particular example.
Next a custom validator function is defined to make sure a phone number in the standard USA format is created.
The model for the Customer object is defined, using several of the built in types that Ottoman supports. For additional reference, see ottomanjs.com. Several Indexes are defined along with the model. The indexes are utilized as methods for each instance of the Customer Object. Ottoman supports complex data types, embedded references to other models, and customization.
In the Customer model above, there are four explicit indexes defined. By default, if an index type is not specified Ottoman will select the fastest available index supported within the current Couchbase cluster. In addition to utilizing built in secondary index support within Couchbase, Ottoman can also utilize referential documents and maintain the referential integrity for updates and deletes. This is a powerful features that allows for blazingly fast lookups by a particular field. This type of index in Ottoman is useful for finding a particular object by a unique field such as customer id or email address in the example above. In addition to any explicit index, Ottoman also provides a generic find capability using the query api and N1QL.
The next block of code in the Customer model is function that allows the application to create and save a new instance of a Customer in one step. The method “create” is a default method provided by Ottoman for objects as a convenience. It's also possible to instantiate a new object, assign all of the fields and then save the object using the “save” method that Ottoman provides for each object. An example of doing it this way is included in the Bike Model below. Lastly, the Ottoman model of the Customer is exported for use in other modules
The bike.js module begins much the same way as the customer.js module. It's important to note here that objects that are referenced by other ottoman objects do not need to be declared specifically as node dependencies modules within other modules. For example, to define a field as a reference in the Bike model to the Customer module defined above, it's not necessary to include the Customer module as a reference.
As in the Customer model example, the Bike object is defined with several different data types, embedded references to other Ottoman models and explicitly defined secondary indexes.
The indexes like in the customer example are a mix of refdoc's and default secondary indexes. Just as in the customer module a shorthand create and save method is utilized for instantiating a new instance of the Bike object.
An example of the same sequence of instantiating a new object, assigning values to all the fields and then saving the object used in the shorthand function above is shown for reference.
The Bike module has a special method that is instantiated with each new instance of the Bike object. This method allows adding a test ride to the array of test rides for logging each specific test ride on a specific bike. Lastly the Bike Model is exported for other modules to use.
Application and Routes
Now that the model is defined above, the controller functionality is defined in the app.js file in the / root directory the routes.js file in the /routes directory.
The app.js file is the entry point to the application and defines how the application will function. The code within the file is as follows:
For the purpose of simplicity, much of the application logic takes place in the route handlers themselves. In a production node application, this functionality would typically be abstracted out along with using middleware to handle things like authentication. Lets look at an example for how new documents are created with the application. The functionality for how a new bike is added into the application is a good place to start, in the routes.js module:
Using the createAndSave method defined in the bike.js module allows us to easily add this entry into the database. Once the entry is added we can use the indexes we defined using Ottoman for the Bike Model as methods to query for a list of bikes, or a specific bike. This example in the routes.js module uses the refdoc index defined for stockID in the Bike Model:
One of the most powerful features in Couchbase, in version 4.0 and later, is the query api. By using sql queries in the N1QL language we can perform structured queries against JSON documents stored within Couchbase. Ottoman makes use of this feature by providing a default generic query method for each type of object you defined with an Ottoman model. The example for how this can be used for the Bike Model is also in the routes.js module:
Similar routes and functionality are defined in the routes.js module for the other two Object types we defined in our model: customers and employees. An Object Mapper rapidly speeds up development time and helps to create a powerful pattern language for the application can interact with complex objects while being completely abstracted from the storage layer. An example for this is the rides array of embedded objects that were defined in the Bike Model. This allows the application to effectively log a list of test rides mapped to the specific customer, specific employee, and stored with each bike. Using Ottoman to model the objects on top of Couchbase, means not have to worry about how those complex relationships are stored in the storage layer, and not having to write all of that code that extracts and represents those stored items to the application. For example, the route that adds a new test ride in the routes.js module:
- Checks if the bike exists for the specified vin number
- Checks if the customer exists for the specified customer email address
- Checks if the employee exists for the specified employee email address
- Adds an entry into the ride history based on the above information, mileage and ride date.
Handling the Object mapping to the storage tier makes life significantly easier and allows for rapid prototyping. Here is the example in the routes.js module that performs the above steps:
Run the Application
To see an actual example of what this looks like with some real data, try running the application and calling the REST API. The examples below use curl, running from a terminal in OSX, with the output piped to a python script “-mjson.tool” for readable JSON
Ottoman improves on the rapid prototyping time to market strategy of using Couchbase with Node.js. An ODM provides the developer a powerful pattern language to design, prototype, and build applications quickly without the code burden of maintaining referential integrity and dependency management. Ride More, Code Less.