Node.js and Windows Azure: A Love Story

June 23, 2013

tags: , ,
no comments

Node.js and Windows Azure work great together. Windows Azure is a perfect host for running Node.js web sites, web services, and other kinds of applications. In my AzureConf and Visual Studio Live! talks this year I’ve been telling the story of this integration, and figured it would be a good idea to document some of the demos I use.

Shameless plug: I will be delivering the Node on Azure talk again at Visual Studio Live! Redmond in August. There is still time to register with an early bird discount and an additional speaker discount if you use the following link: http://bit.ly/RDSPK12Reg

First and foremost: you can find all the code on GitHub and follow through. You will have to use your own Windows Azure subscription, which will require credentials for Table Storage, SQL Database, and other resources – but otherwise you should be able to use the code as is.

The first demo I show is a Node.js application that uses express and socket.io to implement a simple message board – you type in a username and a message, and that message is stored in database and pushed to all connected clients instantaneously. Express provides the scaffolding and socket.io has a very simple API to push updates to all connected clients through WebSockets if supported and through other fallbacks if not.

In the first demo, the underlying database is simply the nstore module. It wraps a JSON file on disk and provides some simple CRUD operations on top of it. Here are the GET and POST operations for the message board, respectively:

var messages = nstore.new(‘messages.db’, function () {
    console.log(‘*** Messages database has been initialized’);
});

app.get(‘/’, function (req, res) {
    messages.all(function (err, results) {
        if (err) {
            res.render(‘error.jade’, {pagetitle: ‘Error’, error: err});
            return;
        }
        res.render(‘messages.jade’, {
            pagetitle:’Messages’, messages: results
        });
    });
});

app.post(‘/newmessage’, function (req, res) {
    var newmessage = {
        id: uuid.v1(),
        user: req.body.user,
        text: req.body.text
    };
    messages.save(newmessage.id, newmessage, function (err, key) {
        res.redirect(‘/’);
        io.sockets.emit(‘newmessage’, newmessage);
    });
});

In the preceding code, messages.all retrieves all the objects in the local JSON file. The results are passed to the renderer of messages.jade, which displays a list of messages. To save a message, messages.save takes an id (which can be used for queries) and a JSON object, and stores it in a file. As soon as the store operation completes, the code emits a socket.io message to all connected clients with the message contents.

In the second demo, I mix things up a little bit by requiring that we use a more production-ready storage mechanism. One easy option is Azure Table Storage – it’s incredibly cheap and the API is not much more complex than nstore’s. Here’s what it takes to perform the GET and POST operations with a messages table in Azure Table Storage:

app.get(‘/’, function (req, res) {
    var query = azure.TableQuery.select().from(‘messages’);
    tableService.queryEntities(query, function (err, results) {
        if (err) {
            res.render(‘error.jade’, {pagetitle: ‘Error’, error: err});
            return;
        }
        for (var i = 0; i < results.length; ++i)
            results[i].id = results[i].RowKey;
        res.render(‘messages.jade’, {
            pagetitle:’Messages’, messages: results
        });
    });
});

app.post(‘/newmessage’, function (req, res) {
    var newmessage = {
        PartitionKey: ‘partition’,
        RowKey: uuid.v1(),
        user: req.body.user,
        text: req.body.text
    };
    tableService.insertEntity(‘messages’, newmessage, function (err) {
        io.sockets.emit(‘newmessage’, newmessage);
        res.redirect(‘/’);
    });
});

Everything is pretty much the same, except we now have to think about the explicit concepts of partition key and row key. For a great talk that explains how to design your Azure Table Storage database model, tune in to the dotnetconf video by Caitie McCaffrey.

By the way, there’s no need to host the preceding code in Windows Azure. You can work with Azure Table Storage from an on-premises Node.js application, and move the app itself to Windows Azure only if it makes sense in your scenario.

In the third demo, I replace the underlying storage with a SQL Server database. Although I could use a local SQL Server installation, it’s more educational to use a database hosted in Windows Azure. To access the database from a Node application, we need the msnodesql module, which – at the time of writing – works only on Windows, because it relies on SQL Server Native Client. (This is the only point in the presentation where I have to switch to a Windows virtual machine to demonstrate this integration point.)

app.get(‘/’, function (req, res) {
    sql.query(conn_str, ‘SELECT [User] as [user], [Text] as FROM bbs.Message’, function (err, results) {
        if (err) {
            console.log(JSON.stringify(err));
            res.render(‘error.jade’, {pagetitle: ‘Error’, error: err});
            return;
        }
        res.render(‘messages.jade’, {
            pagetitle:’Messages’, messages: results
        });
    });
});

app.post(‘/newmessage’, function (req, res) {
    //WARNING: This is subject to horrible SQL injection
    var newmessage = {
        user: req.body.user,
        text: req.body.text
    };
    var values = "’" + newmessage.user + "’, ‘" + newmessage.text + "’";
    sql.queryRaw(
        conn_str, ‘INSERT INTO bbs.Message ([User], [Text]) VALUES (‘ + values + ‘)’, function (err) {
        if (err) {
            console.log(‘Error: ‘ + err);
            res.render(‘error.jade’, {pagetitle: ‘Error’, error: err});
            return;
        }
        io.sockets.emit(‘newmessage’, newmessage);
        res.redirect(‘/’);
    });
});

If you try to run this code on-premises and target a SQL database in Azure, you’ll find that the firewall does not allow you through. Indeed, the SQL database does not allow remote connections by default – and you have to configure the firewall to allow your specific range of IP addresses through.

image

Now that we have the integration with a SQL Server database, we can also plug in a mobile service that accesses the same database – and achieve some bonus integration between Windows Azure Mobile Services and a Node web application that runs on Windows Azure. The mobile service’s implementation is outside of the scope of this post, but I encourage you to check out the four-part tutorial I posted a couple of months ago that covers an extensive multi-platform demo application that uses Windows Azure Mobile Services.

Finally, the last demo replaces the SQL Server backing store with MongoDB hosted in an VM on Windows Azure. This is the holy grail of integration and Microsoft openness: you can run a Node app in a Windows Azure Web Site and have it access a MongoDB database that runs in an Ubuntu (Linux) VM.

The app’s code then uses mongoose (a Node ODM for MongoDB) to access the database, as follows:

app.get(‘/’, function (req, res) {
    Message.find(function (err, results) {
        if (err) {
            res.render(‘error.jade’, {pagetitle: ‘Error’, error: err});
            return;
        }
        res.render(‘messages.jade’, {
            pagetitle:’Messages’, messages: results
        });
    });
});

app.post(‘/newmessage’, function (req, res) {
    var newmessage = new Message({
        id: uuid.v1(),
        user: req.body.user,
        text: req.body.text
    });
    newmessage.save(function (err) {
        if (err) {
            res.render(‘error.jade’, {pagetitle: ‘Error’, error: err});
            return;
        }
        io.sockets.emit(‘newmessage’, newmessage);
        res.redirect(‘/’);
    });
});

The Message object is initialized with mongoose as follows to reflect the database document schema for messages:

var messageSchema = new mongoose.Schema({
        id: String,
        user: String,
        text: String
    });
Message = db.model(‘Message’, messageSchema);

To summarize: you can find the full code on GitHub and explore through these demos. Node and Windows Azure are a great fit and although you can run your Node apps locally and enjoy all the integration points shown in this post, you can also consider hosting your Node app – for free – in a Windows Azure Web Site and checking out the benefits of outsourcing yet another source of IT concern. And to conclude on a “subversive” note: Node is cross-platform, so most of the above is applicable to all cloud providers and operating systems out there.


I am posting short links and updates on Twitter as well as on this blog. You can follow me: @goldshtn

Add comment
facebook linkedin twitter email

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>