Building an API in Node.js Using Hapi.js and Azure Table

In this post, I will demonstrate a sample REST API app in a Node.js, by using Hapi.js as the web application framework and Azure Table as the storage. The source code of the demo app available on github at https://github.com/shijuvar/HapiAzure.

The following npm modules are using for this simple REST API.

  • hapi – HTTP Server framework, used for building the REST API app.
  • azure - Windows Azure Client Library for node, used for persisting data  onto Azure Table storage.
  • joi - Object schema validation module for Hapi.js.
  • node-uuid – Generating unique id for Node.

Azure Table Storage

Azure table storage, stores structured data on the Microsoft Azure cloud platform. Azure Table storage is a NoSQL kind of data store, which provides massive scalability with the ability of storing terabytes of structured data. Azure Table entity has three systems properties: RowKey for Id of the Table, PartitionKey for partitioning the Table storage and Timestamp for date information.

Hapi.js

Hapi.js is a rich web development framework for building web applications and services. Hapi is configuration-centric framework which provides validation, caching, authentication, and also supports a plugin architecture for building scalable systems. Hapi is built by WalmartLabs to power their mobile backend services.

Building the Demo App

Let’s build our sample REST API demo with Hapi and Azure Table. In this demo, we are using a Azure Table named bookmarks. For the sake of the demo, we are simply using a Partition Key mybookmarks. Let’s create a config.json file for storing configurations of Azure Table storage.

{
    "STORAGE_NAME": "Your-Storage-Name",
    "STORAGE_KEY": "Your-Storage-Key"
    "PARTITION_KEY": "myBookmarks",
    "TABLE_NAME": "bookmarks"
}

A Helper for Azure Table

Let’s build a helper object for working with Azure Table in the Node.js app. Let’s create azuretable.js into the app folder lib.

var azure = require('azure');
function AzureTable(storageClient, tableName, partitionKey) {
    this.storageClient = storageClient;
    this.tableName = tableName;
    this.partitionKey = partitionKey;
    this.storageClient.createTableIfNotExists(tableName, 
       function tableCreated(error) {
        if(error) {
            throw error;
        }
    });
};

In the above code block, we are using Node.js module azure for working with Azure Table. In the constructor function, we cerate properties for storing Azure storage client object, table name and partition key. We are also create the Azure Table within the constructor function. The property storageClient  represents the Azure storage client object which can be used for working with a Azure Table storage. The complete implementation of the azuretable.js for Create, Read, Update and Delete operations with Table storage, is provided below:

"use strict";
var azure = require('azure');
var uuid = require('node-uuid');
module.exports = AzureTable;
 
function AzureTable(storageClient, tableName, partitionKey) {
    this.storageClient = storageClient;
    this.tableName = tableName;
    this.partitionKey = partitionKey;
    this.storageClient.createTableIfNotExists(tableName,
   function tableCreated(error) {
        if(error) {
            throw error;
        }
    });
};
 
AzureTable.prototype = {
    find: function (query, callback) {
        var self = this;
        self.storageClient.queryEntities(query, 
       function entitiesQueried(error, entities) {
            if (error) {
                callback(error);
            } else {
                callback(null, entities);
            }
        });
    },
 
    addItem: function (item, callback) {
        var self = this;
        item.RowKey = uuid();
        item.PartitionKey = self.partitionKey;
        self.storageClient.insertEntity(self.tableName, item,
       function entityInserted(error) {
            if (error) {
                callback(error);
            }
            callback(null);
        });
    },
 
    updateItem: function (item, callback) {
        var self = this;
        self.storageClient.updateEntity(self.tableName, item, 
       function entityUpdated(error) {
            if (error) {
                callback(error);
            }
            callback(null);
        });
    },
 
    deleteItem: function (rowKey, callback) {
        var self = this;
        self.storageClient.deleteEntity(self.tableName
            , {
                PartitionKey : self.partitionKey
                , RowKey : rowKey
            }
            ,function entityDeleted(error) {
                if (error) {
                    callback(error);
                }
                callback(null);
            });
    }
}

Controller for handling CRUD operations

We have created a helper object for working with Azure Table storage. Let’s create a controller for responding to routes of the Hapi.js app. Let’s create the file bookmark.js into the folder controllers.

"use strict";
var azure = require('azure');
module.exports = BookmarkController;
function BookmarkController(azureTable) {
    this.azureTable = azureTable;
}
BookmarkController.prototype = {
    getBookmarks: function (request, reply) {
        var self = this;
        var query = azure.TableQuery
            .select()
            .from(self.azureTable.tableName)
            .where('PartitionKey eq ?',
                 self.azureTable.partitionKey);
        self.azureTable.find(query,
     function itemsFound(error, items) {
           reply(items);
        });
    },
    insertBookmark: function insertBookmark(request, reply) {
        var self = this;
        var bookmark = {
            title : request.payload.title
            , desc :  request.payload.desc
            , location: request.payload.location
            , priority: request.payload.priority
        };
        self.azureTable.addItem(bookmark, 
       function itemAdded(error) {
            if(error) {
                reply(error);
            }
            else{ reply(bookmark);}
        });
    },
    updateBookmark: function updateBookmark(request, reply) {
        var self = this;
        var bookmark = {
            PartitionKey : self.azureTable.partitionKey
            , RowKey : request.params.rowkey
            , title : request.payload.title
            , desc :  request.payload.desc
            , location: request.payload.location
            , priority: request.payload.priority
        };
        self.azureTable.updateItem(bookmark, 
      function itemUpdated(error) {
            if(error) {
                reply(error);
            }
            else{ reply(bookmark);}
        });
    },
    deleteBookmark: function deleteBookmark(request, reply) {
        var self = this;
        self.azureTable.deleteItem(request.params.rowkey, 
        function itemDeleted(error) {
            if(error) {
                reply(error);
            }
            else{reply({ message : "Item deleted"})}
        });
    }
}

In the above code block, we are taking incoming form field values and simply calling the methods provided by AzureTable object. In the constructor function, we specify a property azureTable for refering the AzureTable object. 

Specifying the routes of the app with Hapi.js

The routing mechanism provided by Hapi, is brilliant than any other Node.js web development framework. Hapi provides object schema validation by using Node.js module Joi. Let’s create a app.js file for specifying the routes with validations provided by Joi module.

var Joi = require("joi");
var azure = require('azure');
var BookmarkController=require('./../controllers/bookmark');
var AzureTable=require('./../lib/azuretable');
var nconf = require('nconf');
nconf.env().file({ file: 'config.json'});
var tableName = nconf.get("TABLE_NAME");
var partitionKey = nconf.get("PARTITION_KEY");
var accountName = nconf.get("STORAGE_NAME");
var accountKey = nconf.get("STORAGE_KEY");
var tableService = azure.createTableService
                       (accountName, accountKey);
 
var azureTable = new AzureTable(azure.createTableService(
accountName, accountKey), tableName, partitionKey);
 
var bookmarkController = new BookmarkController(azureTable);
 
 
var routes =
[
{
method: 'GET',
path: '/bookmarks',
config: {
    handler: bookmarkController.getBookmarks
                             .bind(bookmarkController)
}
},
{
method: 'POST',
path: '/bookmarks',
config: {
   handler: bookmarkController.insertBookmark
                              .bind(bookmarkController),
   validate: {
       payload: {
           title: Joi.string(),
           desc: Joi.string(),
           location: Joi.string(),
           priority: Joi.number().integer().min(1).max(5)
       } }
  }
},
{
method: 'PUT',
path: '/bookmarks/{rowkey}',
config: {
   handler: bookmarkController.updateBookmark
                       .bind(bookmarkController),
   validate: {
       payload: {
           title: Joi.string(),
           desc: Joi.string(),
           location: Joi.string(),
           priority: Joi.number().integer().min(1).max(5)
       } }
  }
},
{
method: 'DELETE',
path: '/bookmarks/{rowkey}',
config: {
   handler: bookmarkController.deleteBookmark
                                .bind(bookmarkController)
  }
}       
];
 
module.exports.routes = function (server) {
    server.route(routes);
};

The beauty of Hapi.js is that you can specify validation for the schema of form payload, query variable and route path. Here we create the BookmarkController object and assign its methods as handler functions of routes in the Hapi http server. The azure module’s createTableService method creates a Table storage client which passes to the constructor function of AzureTable object along with Table Name and Partition Key.

Let’s create the http server with Hapi

var Hapi = require('hapi');
var app = require('./routes/app');
 
var port = process.env.PORT || 3000;
var host = process.env.HOST || 'localhost';
var server = new Hapi.Server(host,port,{ cors: true });
app.routes(server);
 
server.start(function() {
    console.log("Hapi server started @ " + server.info.uri);
});

Source Code of the demo app

The code block structure of the app is provided below:

image

You can download the source code from https://github.com/shijuvar/HapiAzure.

 

You can follow me on Twitter @shijucv

No Comments