Feed Sign in with OpenID OpenID

Simon Willison’s Weblog

Node.js is genuinely exciting

I gave a talk on Friday at Full Frontal, a new one day JavaScript conference in my home town of Brighton. I ended up throwing away my intended topic (JSONP, APIs and cross-domain security) three days before the event in favour of a technology which first crossed my radar less than two weeks ago.

That technology is Ryan Dahl’s Node. It’s the most exciting new project I’ve come across in quite a while.

At first glance, Node looks like yet another take on the idea of server-side JavaScript, but it’s a lot more interesting than that. It builds on JavaScript’s excellent support for event-based programming and uses it to create something that truly plays to the strengths of the language.

Node describes itself as “evented I/O for V8 javascript”. It’s a toolkit for writing extremely high performance non-blocking event driven network servers in JavaScript. Think similar to Twisted or EventMachine but for JavaScript instead of Python or Ruby.

Evented I/O?

As I discussed in my talk, event driven servers are a powerful alternative to the threading / blocking mechanism used by most popular server-side programming frameworks. Typical frameworks can only handle a small number of requests simultaneously, dictated by the number of server threads or processes available. Long-running operations can tie up one of those threads—enough long running operations at once and the server runs out of available threads and becomes unresponsive. For large amounts of traffic, each request must be handled as quickly as possible to free the thread up to deal with the next in line.

This makes certain functionality extremely difficult to support. Examples include handling large file uploads, combining resources from multiple backend web APIs (which themselves can take an unpredictable amount of time to respond) or providing comet functionality by holding open the connection until a new event becomes available.

Event driven programming takes advantage of the fact that network servers spend most of their time waiting for I/O operations to complete. Operations against in-memory data are incredibly fast, but anything that involves talking to the filesystem or over a network inevitably involves waiting around for a response.

With Twisted, EventMachine and Node, the solution lies in specifying I/O operations in conjunction with callbacks. A single event loop rapidly switches between a list of tasks, firing off I/O operations and then moving on to service the next request. When the I/O returns, execution of that particular request is picked up again.

(In the talk, I attempted to illustrate this with a questionable metaphor involving hamsters, bunnies and a hyperactive squid).

What makes Node exciting?

If systems like this already exist, what’s so exciting about Node? Quite a few things:

  • JavaScript is extremely well suited to programming with callbacks. Its anonymous function syntax and closure support is perfect for defining inline callbacks, and client-side development in general uses event-based programming as a matter of course: run this function when the user clicks here / when the Ajax response returns / when the page loads. JavaScript programmers already understand how to build software in this way.
  • Node represents a clean slate. Twisted and EventMachine are hampered by the existence of a large number of blocking libraries for their respective languages. Part of the difficulty in learning those technologies is understanding which Python or Ruby libraries you can use and which ones you have to avoid. Node creator Ryan Dahl has a stated aim for Node to never provide a blocking API—even filesystem access and DNS lookups are catered for with non-blocking callback based APIs. This makes it much, much harder to screw things up.
  • Node is small. I read through the API documentation in around half an hour and felt like I had a pretty comprehensive idea of what Node does and how I would achieve things with it.
  • Node is fast. V8 is the fast and keeps getting faster. Node’s event loop uses Marc Lehmann’s highly regarded libev and libeio libraries. Ryan Dahl is himself something of a speed demon—he just replaced Node’s HTTP parser implementation (already pretty speedy due to it’s Ragel / Mongrel heritage) with a hand-tuned C implementation with some impressive characteristics.
  • Easy to get started. Node ships with all of its dependencies, and compiles cleanly on Snow Leopard out of the box.

With both my JavaScript and server-side hats on, Node just feels right. The APIs make sense, it fits a clear niche and despite its youth (the project started in February) everything feels solid and well constructed. The rapidly growing community is further indication that Ryan is on to something great here.

What does Node look like?

Here’s how to get Hello World running in Node in 7 easy steps:

  1. git clone git://github.com/ry/node.git (or download and extract a tarball)
  2. ./configure
  3. make (takes a while, it needs to compile V8 as well)
  4. sudo make install
  5. Save the below code as helloworld.js
  6. node helloworld.js
  7. Visit http://localhost:8080/ in your browser

Here’s helloworld.js:

var sys = require('sys'), 
  http = require('http');

http.createServer(function(req, res) {
  res.sendHeader(200, {'Content-Type': 'text/html'});
  res.sendBody('<h1>Hello World</h1>');
  res.finish();
}).listen(8080);

sys.puts('Server running at http://127.0.0.1:8080/');

If you have Apache Bench installed, try running ab -n 1000 -c 100 ’http://127.0.0.1:8080/’ to test it with 1000 requests using 100 concurrent connections. On my MacBook Pro I get 3374 requests a second.

So Node is fast—but where it really shines is concurrency with long running requests. Alter the helloworld.js server definition to look like this:

http.createServer(function(req, res) {
  setTimeout(function() {
    res.sendHeader(200, {'Content-Type': 'text/html'});
    res.sendBody('<h1>Hello World</h1>');
    res.finish();
  }, 2000);
}).listen(8080);

We’re using setTimeout to introduce an artificial two second delay to each request. Run the benchmark again—I get 49.68 requests a second, with every single request taking between 2012 and 2022 ms. With a two second delay, the best possible performance for 1000 requests 100 at a time is 1000 requests / (1000 / 100) * 2 seconds = 50 requests a second. Node hits it pretty much bang on the nose.

The most important line in the above examples is res.finish(). This is the mechanism Node provides for explicitly signalling that a request has been fully processed and should be returned to the browser. By making it explicit, Node makes it easy to implement comet patterns like long polling and streaming responses—stuff that is decidedly non trivial in most server-side frameworks.

djangode

Node’s core APIs are pretty low level—it has HTTP client and server libraries, DNS handling, asynchronous file I/O etc, but it doesn’t give you much in the way of high level web framework APIs. Unsurprisingly, this has lead to a cambrian explosion of lightweight web frameworks based on top of Node—the projects using node page lists a bunch of them. Rolling a framework is a great way of learning a low-level API, so I’ve thrown together my own—djangode—which brings Django’s regex-based URL handling to Node along with a few handy utility functions. Here’s a simple djangode application:

var dj = require('./djangode');

var app = dj.makeApp([
  ['^/$', function(req, res) {
    dj.respond(res, 'Homepage');
  }],
  ['^/other$', function(req, res) {
    dj.respond(res, 'Other page');
  }],
  ['^/page/(\\d+)$', function(req, res, page) {
    dj.respond(res, 'Page ' + page);
  }]
]);
dj.serve(app, 8008);

djangode is currently a throwaway prototype, but I’ll probably be extending it with extra functionality as I explore more Node related ideas.

nodecast

My main demo in the Full Frontal talk was nodecast, an extremely simple broadcast-oriented comet application. Broadcast is my favourite “hello world” example for comet because it’s both simpler than chat and more realistic—I’ve been involved in plenty of projects that could benefit from being able to broadcast events to their audience, but few that needed an interactive chat room.

The source code for the version I demoed can be found on GitHub in the no-redis branch. It’s a very simple application—the client-side JavaScript simply uses jQuery’s getJSON method to perform long-polling against a simple URL endpoint:

function fetchLatest() {
  $.getJSON('/wait?id=' + last_seen, function(d) {
    $.each(d, function() {
      last_seen = parseInt(this.id, 10) + 1;
      ul.prepend($('<li></li>').text(this.text));
    });
    fetchLatest();
  });
}

Doing this recursively is probably a bad idea since it will eventually blow the browser’s JavaScript stack, but it works OK for the demo.

The more interesting part is the server-side /wait URL which is being polled. Here’s the relevant Node/djangode code:

var message_queue = new process.EventEmitter();

var app = dj.makeApp([
  // ...
  ['^/wait$', function(req, res) {
    var id = req.uri.params.id || 0;
    var messages = getMessagesSince(id);
    if (messages.length) {
      dj.respond(res, JSON.stringify(messages), 'text/plain');
    } else {
      // Wait for the next message
      var listener = message_queue.addListener('message', function() {
        dj.respond(res, 
          JSON.stringify(getMessagesSince(id)), 'text/plain'
        );
        message_queue.removeListener('message', listener);
        clearTimeout(timeout);
      });
      var timeout = setTimeout(function() {
        message_queue.removeListener('message', listener);
        dj.respond(res, JSON.stringify([]), 'text/plain');
      }, 10000);
    }
  }]
  // ...
]);

The wait endpoint checks for new messages and, if any exist, returns immediately. If there are no new messages it does two things: it hooks up a listener on the message_queue EventEmitter (Node’s equivalent of jQuery/YUI/Prototype’s custom events) which will respond and end the request when a new message becomes available, and also sets a timeout that will cancel the listener and end the request after 10 seconds. This ensures that long polls don’t go on too long and potentially cause problems—as far as the browser is concerned it’s just talking to a JSON resource which takes up to ten seconds to load.

When a message does become available, calling message_queue.emit(’message’) will cause all waiting requests to respond with the latest set of messages.

Talking to databases

nodecast keeps track of messages using an in-memory JavaScript array, which works fine until you restart the server and lose everything. How do you implement persistent storage?

For the moment, the easiest answer lies with the NoSQL ecosystem. Node’s focus on non-blocking I/O makes it hard (but not impossible) to hook it up to regular database client libraries. Instead, it strongly favours databases that speak simple protocols over a TCP/IP socket—or even better, databases that communicate over HTTP. So far I’ve tried using CouchDB (with node-couch) and redis (with redis-node-client), and both worked extremely well. nodecast trunk now uses redis to store the message queue, and provides a nice example of working with a callback-based non-blocking database interface:

var db = redis.create_client();
var REDIS_KEY = 'nodecast-queue';

function addMessage(msg, callback) {
  db.llen(REDIS_KEY, function(i) {
    msg.id = i; // ID is set to the queue length
    db.rpush(REDIS_KEY, JSON.stringify(msg), function() {
      message_queue.emit('message', msg);
      callback(msg);
    });
  });
}

Relational databases are coming to Node. Ryan has a PostgreSQL adapter in the works, thanks to that database already featuring a mature non-blocking client library. MySQL will be a bit tougher—Node will need to grow a separate thread pool to integrate with the official client libs—but you can talk to MySQL right now by dropping in DBSlayer from the NY Times which provides an HTTP interface to a pool of MySQL servers.

Mixed environments

I don’t see myself switching all of my server-side development over to JavaScript, but Node has definitely earned a place in my toolbox. It shouldn’t be at all hard to mix Node in to an existing server-side environment—either by running both behind a single HTTP proxy (being event-based itself, nginx would be an obvious fit) or by putting Node applications on a separate subdomain. Node is a tempting option for anything involving comet, file uploads or even just mashing together potentially slow loading web APIs. Expect to hear a lot more about it in the future.

Further reading

This is Node.js is genuinely exciting by Simon Willison, posted on 23rd November 2009.

Tagged , , , , , , , , , , , ,

View blog reactions

Next: Crowdsourced document analysis and MP expenses

Previous: Why I like Redis

44 comments

  1. Thanks Simon - a great overview. Shame I missed FullFrontal and your actual presentation, would have been great to see it demoed live :-(

    Mark Perkins - 23rd November 2009 13:52 - #

  2. Seems like the ideal sort of thing for Google to implement native Javascript on AppEngine.

    Rob Crowther - 23rd November 2009 15:17 - #

  3. I started to work on a djangonode as soon as I heard about Node.js, nice job!

    There are a lot of things to do with node and this idea is really interesting.

    I'm watching you @ github, really curious to find out how you will solve the template matter.

    Samori Gorse - 23rd November 2009 16:15 - #

  4. I haven't thought about templating very hard yet.

    There's a direct port of the Django templating language to JavaScript in Dojo, so it would be possible to extract and use that (though probably non trivial as you'd need to get rid of all of the Dojo dependencies).

    TrimPath is worth a look as well.

    Simon Willison - 23rd November 2009 17:28 - #

  5. Looks like you reinvented the Wheel Smalltalk has shaped years ago...

    Smalltalker - 23rd November 2009 18:02 - #

  6. @Simon Willison : You should probably check that project http://bitbucket.org/masklinn/jsdt/

    Samori Gorse - 23rd November 2009 18:13 - #

  7. Out of curiosity, why do you not see yourself switching to SSJS? Too much invested in python?

    Karl - 23rd November 2009 18:43 - #

  8. JSON Template works fine with Node.js. I just had to add a module.exports() call to the end of json-template.js file to export all the functions.

    Jeethu Rao - 23rd November 2009 19:08 - #

  9. For templating there is also http://github.com/janl/mustache.js

    Jason Davies - 23rd November 2009 22:20 - #

  10. Nice
    Article.
    I
    especially
    liked
    the
    skinny
    column
    format.

    Twiggy - 24th November 2009 01:14 - #

  11. I wonder if large-volume sites could use this to deliver some of their more static, highly-visited pages. I'm thinking, for example, the CNN homepage or the NY Times homepage.

    The sites could move the high-volume pages onto a Node-powered setup without too much effort, and could handle huge surges of traffic with greater ease.

    Bret - 24th November 2009 02:35 - #

  12. Django's regex routes are nice, all but I humbly propose using URI templates:
    http://www.dashdashverbose.com/2009/11/request-rou ting-with-uri-templates-in.html

    Sean McCullough - 24th November 2009 05:16 - #

  13. Are we going to see a Nails framework? :)

    Simon Harris - 24th November 2009 05:38 - #

  14. Implementing MySQL support should be not so hard as you described, just use the Drizzle client library instead of using a MySQL one. It's compatible with MySQL servers, and additionally have a non-blocking interface as well.

    Andras Barthazi - 24th November 2009 18:52 - #

  15. How about DBSlayer for MySQL?

    http://code.nytimes.com/projects/dbslayer

    johnwards - 24th November 2009 20:11 - #

  16. Oh god I'm an idiot and didn't read to the bottom.

    Off to smack myself around the head.

    johnwards - 24th November 2009 20:13 - #

  17. Which Macbook Pro are you running this on Simon? The 13"?

    Peter - 25th November 2009 10:52 - #

  18. I don't think that `fetchLatest` is actually recursive. The "recursive" call is in an asynchronous callback; so each invocation will return before the next call. Anyway I think the stack size will be fine :)

    Jesse Hallett - 27th November 2009 09:51 - #

  19. I don't entirely get the point of Twisted being hampered by the large number of libraries for Python. Node is barebones because JS doesn't have a large number of libraries. If you have to roll your own using asyncore in python, seems that they are at an equal starting point?

    Blank slate in this case is a state of mind.

    -Preston

    Preston Holmes - 28th November 2009 08:59 - #

  20. @Preston

    For network-only stuff, you're pretty much correct. The difference with node is that EVERYTHING is asynchronous including disk reads/writes and system calls. Additionally, all libraries written to work with node are asynchronous so you don't hit that point where you need to connect to your DB and ask "now what?"

    Karl - 28th November 2009 21:44 - #

  21. the function extractPost in djangode stopped working in version 0.1.22 of node.js. here is a fix:

    exports.extractPost = function(req, callback) {
    req.setBodyEncoding('utf-8');
    var body = '';
    req.addListener('body', function(chunk) {
    body += chunk;
    });
    req.addListener('complete', function() {
    var temp = uri.parse('http://fake/?' + body);
    temp.params = {};
    var parts = temp.query.split('&');
    for (var j = 0; j < parts.length; j++) {
    var i = parts[j].indexOf('=');
    if (i < 0) continue;
    try {
    var key = uri.decode(parts[j].slice(0,i));
    var value = uri.decode(parts[j].slice(i+1));
    temp.params[key] = value;
    } catch (e) {
    continue;
    }
    }
    callback(temp.params);
    });
    }

    luff - 22nd December 2009 13:04 - #

  22. If you want to remove the recursion on fetchLatest(), just make it an async call using setTimeout:

    setTimeout(function() { fetchLatest(); }, 0);

    Jeoff Wilks - 15th January 2010 21:37 - #

  23. So influenced by this article,thanks!

    frank - 26th August 2010 07:35 - #

  24. I can't believe you're not palynig with me--that was so helpful.

    Clara - 9th September 2011 04:02 - #

  25. Very interesting and informative blog. Hope we get some updates

    Tool Steel - 7th October 2011 02:34 - #

  26. Node.js is really a superb work. Really enjoyed this great discussion.

    gaming club Rico - 9th October 2011 17:23 - #

  27. Thank you for the posts. I found the information to be informative and useful. roof repairs manteca

    cy - 12th October 2011 05:14 - #

  28. There are plenty of information about this topic in the net some are definitely better than others. mensagens para orkut | iPhone jailbreak

    hunny - 18th October 2011 12:45 - #

  29. Good stuff as per usual, thanks. I do hope this kind of thing gets more exposure.

    tool steel - 25th October 2011 02:04 - #

  30. This is unique and it really feels good to know when you got a chance to know about something new. Clients select Absolute Source because of our trained and certified members. Nice business workouts which could be helped us much better.. Clubmz espy

    lucrisasalloni - 25th October 2011 11:15 - #

  31. Thanks for the link for Full Frontal, I'll check it out. disney credit card

    ash - 25th October 2011 13:36 - #

  32. nice job

    no fee debit dc - 26th October 2011 12:35 - #

  33. Unlike other your piece of writing has a zeal that matters to your readers.

    assignment writing - 28th October 2011 13:40 - #

  34. Great article :)

    Traveler - 29th October 2011 19:44 - #

  35. hi its really exciting

    Christmas Cake Recipes - 1st November 2011 10:11 - #

  36. Programmers have their own style in writing codes. And they have their own favourite programming languages. seo company

    seo company - 2nd November 2011 18:16 - #

  37. this is a great article that you have here.. thanks for sharing scrabble cheat

    Rozario - 3rd November 2011 05:09 - #

  38. I have no point to raise in against of what you have said.you possess lots of understanding on this subject.

    logo design - 5th November 2011 04:19 - #

  39. I have no point to raise in against of what you have said.you possess lots of understanding on this subject.

    logo design - 5th November 2011 04:20 - #

  40. Your blog is very impressive and I really appreciate your well written articles and tutorials. This will really help me with my current project and expand my knowledge. I want to thank you for you sincerely and keep up the good work, I appreciate this.

    Brendan

    <a href="http://stuffy-nose-remedy.net">stuffy nose remedy</a>

    stuffy nose remedy - 5th November 2011 20:43 - #

  41. Your blog is very impressive and I really appreciate your well written articles and tutorials. This will really help me with my current project and expand my knowledge. I want to thank you for you sincerely and keep up the good work, I appreciate this.

    Brendan

    http://stuffy-nose-remedy.net

    stuffy nose remedy - 5th November 2011 20:53 - #

  42. There is nothing better then using javascript. I use it all the time at work with great success. The idea behind this conference is so good. You will get so much great information from it. This is a must to attend. Business Grant

    jack franks - 6th November 2011 13:47 - #

  43. I do hope this kind of thing gets more exposure.pellet mill auxiliary equipment

    fuqin - 8th November 2011 01:27 - #

  44. Excellent site, keep up the good work my colleagues would love this.. Thanks.robert michael couch

    ghrency - 9th November 2011 02:10 - #

Comments are closed.
A django site