NodeJS Tutorial – Part 2, Routing

Note: This is the second part of my NodeJS tutorial. You may want to go back to the first part of this tutorial if you haven’t already read it.

In the first part of this tutorial we looked at reading a document from CouchDB and keeping our markup separate from our code using Haml templates. We arrived at the point where we clicked on a link, but nothing happened. Today we are going to put that right with some very simple routing.

The URL Module

The basis of all routing is the URL. Fortunately, Node has some basic functions which handle reading the URL so we can then decide what the user is actually trying to do.

First, we want to make sure we have included the URL module.

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

The next step is to extrapolate the path name from the URL that the user has visited:

var url_parts = url.parse(req.url);
sys.puts(url_parts.pathname);

Now, when I go to http://127.0.0.1:8000/create in Chromium, the system output is as follows:
/create
/favicon.ico

As you can see I have to deal with /create but also /favicon.ico – unfortunately I don’t have one. We’ll deal with that later by using a 404 error.

The pages we want to be dealing with are as follows:

  • /
  • create
  • /edit

For simplicity’s sake we’ll put them into a switch statement:

switch(url_parts.pathname) {
case '/':
sys.puts("display root");
break;
case '/create':
sys.puts("display create");
break;
case '/edit':
sys.puts("display edit");
break;
default:
sys.puts("oh dear, 404");
}

If you test this out you should see the relevant text displayed in the standard output.

Subscribe to the mailing list

If you are enjoying this post, please consider signing up to the mailing list.


Tidy up Existing Code

Now we have a basic router telling us that it has detected which page the user wants to display, but is then displaying the default page regardless. To correct this we need to put our existing code within a function and then call that function in the relevant part of the switch statement.

So, create a new (sub) function called display_root() in the main bulk of the http.createServer() function, expecting a url, a server request object and aserver response object as parameters:

function display_root(url, req, res) {
...
}

Within the body of the function, you want to insert the main bulk of the application that we wrote last time. I have included the complete code at the bottom of this tutorial, and all code is of course on GitHub.

Now call this function within the switch statement:

display_root(url_parts.pathname, req, res);

Handling Missing Pages

As said, Chromium is looking for a favicon.ico file, but currently this application doesn’t have one. As such it needs to be able to handle missing files gracefully, with the correct 404 response.

Create a new function within the main bulk of the http.createServer() function:

function display_404(url, req, res) {
res.writeHead(404, {'Content-Type': 'text/html'});
res.write("<h1>404 Not Found</h1>");
res.end("The page you were looking for: "+url+" can not be found");
}

Notice that we have specified the header to be 404.

Now you can call this from within the switch:

display_404(url_parts.pathname, req, res);

You can test it out by going to http://127.0.0.1:8000/qwerty
You should see:

404 Not Found
The page you were looking for: /qwerty can not be found

We could use a Haml template to move the content out of the code, but for the purposes of this tutorial it will suffice.

The Form

Now we want to generate a “Create” page – which will be a form allowing us to enter a list name and 1-5 items. (Note: I have changed from 10 items to 5 for no other reason that I am lazy, and don’t particularly want 10 things on my list, also it keeps the code a bit more clean!)

First, create the Haml template that will be used. We’ll keep this very simple for now. The key part is the form generation. After some trial and error, this mark-up seemed to work:

%form(action=url method="post")
      :each i in [0,1,2,3,4]
        %p %label Item
           %input(type="text" name="item[]")
      %p
        %input(type="submit" value="Create List")

You may want to read up on Haml here: http://haml-lang.com

The function for displaying the page is as follows:

    function display_create(url, req, res) {
	res.writeHead(200, {'Content-Type': 'text/html'});
	fs.readFile('./templates/create.haml', function(e, c) {
	    var data = {
		title: "Create New List",
		message: "Please enter up to 5 things to remember",
		url: url
	    };
	    var html = haml.render(c.toString(), {locals: data});
	    res.end(html);
	});
    }

Now if you fill in the items and submit, the page simply refreshes itelf. We need to get hold of the posted form data and do something useful with it.

This will be what we’ll focus on next time.

Please help improve this blog and take a quick survey. Thanks!

Tutorial Recap Part 2

In part 2 of the Node JS tutorial, we started to explore some very basic routing techniques. A number of dedicated routing modules have sprung up, of two of which are listed here:

Complete Code

var sys = require('sys'),
    fs = require('fs'),
    http = require('http'),
    url = require('url');
 
// Node-CouchDB: http://github.com/felixge/node-couchdb
var couchdb = require('./libs/node-couchdb/lib/couchdb'),
    client = couchdb.createClient(5984, 'localhost'),
    db = client.db('erdnodeflips');
 
// Haml-js: http://github.com/creationix/haml-js
var haml = require('./libs/haml-js/lib/haml');
 
var doc_id = '2d36f401bc4b82c9160e1a4ea936aba3';
 
http.createServer(function (req, res) {
    var url_parts = url.parse(req.url);
 
    switch(url_parts.pathname) {
    case '/':
	display_root(url_parts.pathname, req, res);
	break;
    case '/create':
	display_create(url_parts.pathname, req, res);
	break;
    case '/edit':
	sys.puts("display edit");
	break;
    default:
	display_404(url_parts.pathname, req, res);
    }
    return;
 
    /**
     * Display the document root
     **/
    function display_root(url, req, res) {
	res.writeHead(200, {'Content-Type': 'text/html'});
	db.getDoc(doc_id, function(error, doc) {
	    if(error) {
		fs.readFile('./templates/no-doc.haml', function(e, c) {
		    var data = {
			title: "No Document Found",
			message: "No document could be found",
			link: "/create",
			link_text: "Create a new document"
		    };
		    var html = haml.render(c.toString(), {locals: data});
		    res.end(html);
		});
	    }
	    else {
		fs.readFile('./templates/doc.haml', function(e, c) {
		    var data = {
			title: "Erdnodeflip document: "+doc.name,
			message: "Your Erdnusflip document was found!",
			items: doc.items,
		    };
		    var html = haml.render(c.toString(), {locals: data});
		    res.end(html);
		});
	    }
	});
    }
 
    /**
     * Display the list creat page
     **/
    function display_create(url, req, res) {
	res.writeHead(200, {'Content-Type': 'text/html'});
	fs.readFile('./templates/create.haml', function(e, c) {
	    var data = {
		title: "Create New List",
		message: "Please enter up to 5 things to remember",
		url: url
	    };
	    var html = haml.render(c.toString(), {locals: data});
	    res.end(html);
	});
    }
 
    /**
     * Display the 404 page for content that can't be found
     **/
    function display_404(url, req, res) {
	res.writeHead(404, {'Content-Type': 'text/html'});
	res.write("
<h1>404 Not Found</h1>
");
	res.end("
 
The page you were looking for: "+url+" can not be found");
    }
}).listen(8000);
sys.puts('Server running at http://127.0.0.1:8000/');
  • Fwallenborn

    great. but how can i route the css an images requests at once? im currently doing it like that:

    server = http.createServer(function(req, res){
    // your normal server code
    var path = url.parse(req.url).pathname;
    switch (path){
    case ‘/’:
    res.writeHead(200, {‘Content-Type’: ‘text/html’});
    res.write(‘Welcome. Try the chat example.’);
    res.end();
    break;
    case ‘/game.js’:
    case ‘/wall.png’:
    case ‘/helpers.js’:
    case ‘/map.js’:
    case ‘/json.js’:
    case ‘/connection.js’:
    case ‘/game.html’:
    fs.readFile(__dirname + path, function(err, data){
    if (err) return send404(res);
    res.writeHead(200, {‘Content-Type’: path == ‘json.js’ ? ‘text/javascript’ : ‘text/html’})
    res.write(data, ‘utf8′);
    res.end();
    });
    break;

  • Fwallenborn

    great. but how can i route the css an images requests at once? im currently doing it like that:

    server = http.createServer(function(req, res){
    // your normal server code
    var path = url.parse(req.url).pathname;
    switch (path){
    case '/':
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write('

    Welcome. Try the chat example.

    ');
    res.end();
    break;
    case '/game.js':
    case '/wall.png':
    case '/helpers.js':
    case '/map.js':
    case '/json.js':
    case '/connection.js':
    case '/game.html':
    fs.readFile(__dirname + path, function(err, data){
    if (err) return send404(res);
    res.writeHead(200, {'Content-Type': path == 'json.js' ? 'text/javascript' : 'text/html'})
    res.write(data, 'utf8');
    res.end();
    });
    break;

  • http://twitter.com/Skizzles Josh Martin

    Here is a chunk of code that will route your css to compiled sass. No cache headers yet, but it should work for development.

    // Dynamic Stylesheet Support
    app.get(‘/css/*.css’, function(req, res){
    fn = ‘sass/’ + req.params + ‘.sass’;
    path.exists(fn, function() {
    res.header(‘Content-Type’,'text/css; charset=utf-8′);
    res.render(fn, { layout: false});
    });
    });

  • http://twitter.com/Skizzles Josh Martin

    Here is a chunk of code that will route your css to compiled sass. No cache headers yet, but it should work for development.

    // Dynamic Stylesheet Support
    app.get('/css/*.css', function(req, res){
    fn = 'sass/' + req.params + '.sass';
    path.exists(fn, function() {
    res.header('Content-Type','text/css; charset=utf-8');
    res.render(fn, { layout: false});
    });
    });

  • Pingback: JavaScript as a Server-Side Scripting Language | Envano a Green Bay, WI Interactive Marketing Agency

  • Pingback: JavaScript as a Server-Side Scripting Language – Envano Interactive Marketing Blog