Browse By

Windows Azure DocumentDB in NodeJS app

I’ve read a few weeks ago a small article about DocumentDB in Windows Azure which is Microsoft’s NoSQL solution in cloud, but I didn’t have time to go more in depth, until today! I came across this wonderful tutorial which explains in detail how to develop a small CRUD application in NodeJS which stores the information in DocumentDB.

In short:

  1. Assuming that you have an azure account, you are logging in to https://portal.azure.com/ and you are creating a DocumentDB database by clicking the big + button at the bottom
  2. If your favorite IDE is Visual Studio like me then install NodeJs Tools from here
  3. Add nconf and documentdb modules through npm. Add config.json file with your database details
  4.  Replace code in routes/index.js with this:
// import the modules we will use
var DocumentDBClient = require('documentdb').DocumentClient;
var nconf = require('nconf');

// tell nconf which config file to use
nconf.env();
nconf.file({ file: 'config.json' });

var host = nconf.get("HOST");
var authKey = nconf.get("AUTH_KEY");
var databaseId = nconf.get("DATABASE");
var collectionId = nconf.get("COLLECTION");

// create some global variables which we will use later to hold instances of the DocumentDBClient, Database and Collection

// create an instance of the DocumentDB client
var client = new DocumentDBClient(host, { masterKey: authKey });

exports.index = function (req, res) {
    // before we can query for Items in the document store, we need to ensure we 
    // have a database with a collection then use the collection to read the documents
    readOrCreateDatabase(function (database) {
        readOrCreateCollection(database, function (collection) {
            listItems(collection, function (items) {
                res.render('index', { title: 'My ToDo List', tasks: items });
            });
        });
    });
};

exports.createOrUpdateItem = function (req, res) {
    //first have to set the database & collection context so that we have the self links   
    readOrCreateDatabase(function (database) {
        readOrCreateCollection(database, function (collection) {
            
            //if we find an item on the form, we'll create it in the database
            var item = req.body.item;
            if (item) {
                createItem(collection, item, function () {
                    res.redirect('/');
                });

            //else let's look for items marked as completed, 
            //and update that item in the database
            } else {
                var completed = req.body.completed;
                
                //check if completed is actually an Array, if not make it one. 
                //this happens when you select only one item            
                if (!completed.forEach)
                    completed = [completed];
                
                //use a recursive function to loop through items in 
                //array calling updateItem each time through                                    
                function updater(i) {
                    if (i < completed.length) {
                        updateItem(collection, completed[i], function () {
                            updater(i + 1);
                        });
                    } else {
                        res.redirect('/');
                    }
                }
                
                //kick off the recursion
                updater(0);
            }
        });
    });
}

// update item
var updateItem = function (collection, itemId, callback) {
    //first fetch the document based on the id
    getItem(collection, itemId, function (doc) {
        //now replace the document with the updated one
        doc.completed = true;
        client.replaceDocument(doc._self, doc, function (err, replacedDoc) {
            if (err) {
                throw (err);
            }
            
            callback();
        });
    });
}

// get item
var getItem = function (collection, itemId, callback) {
    client.queryDocuments(collection._self, 'SELECT * FROM root r WHERE r.id="' + itemId + '"').toArray(function (err, results) {
        if (err) {
            throw (err);
        }
        
        callback(results[0]);
    });
}

// create new item
var createItem = function (collection, documentDefinition, callback) {
    documentDefinition.completed = false;
    client.createDocument(collection._self, documentDefinition, function (err, doc) {
        if (err) {
            throw (err);
        }
        
        callback();
    });
}

// query the provided collection for all non-complete items
var listItems = function (collection, callback) {
    client.queryDocuments(collection._self, 'SELECT * FROM root r WHERE r.completed=false').toArray(function (err, docs) {
        if (err) {
            throw (err);
        }
        
        callback(docs);
    });
}

// if the database does not exist, then create it, else return the database object
var readOrCreateDatabase = function (callback) {
    client.queryDatabases('SELECT * FROM root r WHERE r.id="' + databaseId + '"').toArray(function (err, results) {
        if (err) {
            // some error occured, rethrow up
            throw (err);
        }
        if (!err && results.length === 0) {
            // no error occured, but there were no results returned 
            // indicating no database exists matching the query            
            client.createDatabase({ id: databaseId }, function (err, createdDatabase) {
                callback(createdDatabase);
            });
        } else {
            // we found a database
            callback(results[0]);
        }
    });
};

// if the collection does not exist for the database provided, create it, else return the collection object
var readOrCreateCollection = function (database, callback) {
    client.queryCollections(database._self, 'SELECT * FROM root r WHERE r.id="' + collectionId + '"').toArray(function (err, results) {
        if (err) {
            // some error occured, rethrow up
            throw (err);
        }
        if (!err && results.length === 0) {
            // no error occured, but there were no results returned 
            //indicating no collection exists in the provided database matching the query
            client.createCollection(database._self, { id: collectionId }, function (err, createdCollection) {
                callback(createdCollection);
            });
        } else {
            // we found a collection
            callback(results[0]);
        }
    });
};

5. App.js file should look like this

/**
 * Module dependencies.
 */

var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');

var app = express();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(require('stylus').middleware(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
    app.use(express.errorHandler());
}

app.get('/', routes.index);
app.post('/', routes.createOrUpdateItem);

http.createServer(app).listen(app.get('port'), function () {
    console.log('Express server listening on port ' + app.get('port'));
});

6. Then create your form in jade

Result should be something like this:

documentdbss

Leave a Reply

Your email address will not be published. Required fields are marked *