boldr by Nicolas Mérouze

Create a Web App with Node.js

Node.js is the new hype for server-side webish things. It's evented, non-blocking, fast and in javascript (not another language to learn, yay!). Real-Time Web is around the corner.

Javascript means you can use existing JS librairies. In this example I will use Mustache.js and Underscore to create the core of a minimal web application.

Let's begin

I have 4 files: main.js, mustache.js, underscore.js, templates/index.html

We will put our code in main.js. First we require the things we'll need:

var sys = require('sys'), http = require('http'), posix = require('posix')
require('./underscore')
var Mustache = require('./mustache')

Note that Underscore is CommonJS compliant so you don't have to do anything for it. Then we add our actions which are composed by 3 things: URI, template name and the view.

var actions = [];

actions.push({
  path: "/",
  template: "index.html",
  view: {
    title: "Joe",
    calc: function() { return 2 + 4; }
  }
})

We can create as many actions as we want. Now let's create the server:

http.createServer(function (req, res) {}).listen(8000);

The server will be running but will not know how not handle actions. Let's add a request listener:

http.createServer(function (req, res) {
  req.addListener('complete', function() {})
}).listen(8000);

The req variable contains the URI, so we will select the action corresponding to the request:

http.createServer(function (req, res) {
  req.addListener('complete', function() {
    var action = _(actions).chain().select(function(a) { return req.uri.path == a.path }).first().value()
  })
}).listen(8000);

If the request doesn't match an action, we will send an error:

http.createServer(function (req, res) {
  req.addListener('complete', function() {
    var action = _(actions).chain().select(function(a) { return req.uri.path == a.path }).first().value()

    if (_.isEmpty(action)) {
      res.sendHeader(404, {'Content-Type': 'text/plain'})
      res.sendBody("Error")
      res.finish()
    } else {}
  })
}).listen(8000);

In the other case we render the action:

http.createServer(function (req, res) {
  req.addListener('complete', function() {
    var action = _(actions).chain().select(function(a) { return req.uri.path == a.path }).first().value()

    if (_.isEmpty(action)) {
      res.sendHeader(404, {'Content-Type': 'text/plain'})
      res.sendBody("Error")
      res.finish()
    } else {
      posix.cat("./templates/" + action.template).addCallback(function(template) {
        res.sendHeader(200, {'Content-Type': 'text/html'})
        res.sendBody(Mustache.to_html(template, action.view))
        res.finish()
      })
    }
  })
}).listen(8000);

We get the template content then renders it when the I/O process is complete. Here's the template:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <title>My Framework</title>
</head>

<body>
  {{title}} spends {{calc}}
</body>
</html>

And that's all! It's running and you can create your blog with it for example.

There's already a lot of Node.js frameworks that you can find on GitHub if you search for "node". But there's nothing really mature at this time.

Update (10 Feb 2010)

I forgot to mention that the mustache.js I've used was tweaked. We just have to add this following code at the end of the mustache.js file:

for (var name in Mustache)
    if (Object.prototype.hasOwnProperty.call(Mustache, name))
        exports[name] = Mustache[name];

This will be merge into the main library in the near future and you can find the whole file here

If you liked the article and/or want to talk to me about it, you can follow me on twitter.