Fork me on GitHub

Rationale

Routing, in web app parlance, is about defining what code to execute when a given URL is invoked.

NSR takes care of the necessary plumbing to make that happen, freeing the programmer of the cumbersome details, allowing her to focus on the problem domain.

How does it work?

As was stated in the lines above, it's not necessary to know NSR inner workings in order to be productive using it. Having said that, it is nevertheless useful to have some insight on a couple of key aspects, fundamentally what could be called the "request wrapping mechanism".

When you feed NSR with a url handling function, i.e. router.get("/answertoall", function(request, response) {response.end("42");}); what NSR does is to wrap that function into another, unnamed one, which has the primary mission of "augmenting" the request object and it stores said function in an array of url-handling functions, thus acting as a middleware piece of code.
At run time, when a client invokes the matching URL, the "middleware" function will be called, which, after doing its trickery to "dress" the request object, will ultimately call the original url-handling function that was provided.

What does "augmenting-dressing" the request object mean?
Well, basically, NSR provides the request object with 3 properties:

  • request.get which is an object representation of the query string
  • request.post an object representation of what was posted, if anything
  • request.body is the union of the two previous items
It should be pointed down that regardless the transmission method, NSR takes the necessary steps to make all 3 of them true javascript objects with all that implies, JSON and all. Worst case is an empty object {}, no errors.
So, you can use request.get.whatever for router.get, request.post.whatever for router.post, but in any case, if you don't care about request method, using request.body.whatever is a safe bet, most obviously useful if you do not know in advance the request method, for example: router.any("/threefold", function(request, response) {response.end((parseInt(request.body.number) * 3).toString();});

Wrapping up, you just got to remember request.get, request.post and request.body
And that's all there is about it.

Options

NSR sticks to some conventions ("public" as directory name for static assets, etc),

which the programmer can override when instantiating the router, for instance:

var router = new Router({static_route: __dirname + "/static"});

to change usage of the default "public" directory for static resources

List of default options:


                logging: true
log: console.log
static_route: "#{process.cwd()}/public"
serve_static: true
list_dir: true
default_home: ['index.html', 'index.htm', 'default.htm']
cgi_dir: "cgi-bin"
serve_cgi: true
serve_php: true
php_cgi: "php-cgi"
served_by: 'Node Simple Router'
software_name: 'node-simple-router'
admin_user: 'admin'
admin_pwd: 'admin'

Most of them are self explanatory, but some deserve further comments, which will be added on doc completion.

Router API

Router object supports the following methods

  • get

    Usage:
    router.get('/users/:id', function(request, response) { response.end("User: " + getUserById(request.params.id).fullName);});
  • post

    Usage:
    router.post('/users', function(request, response) { insertUser(request.post.user, function(new_user_id) { request.post.user.id = new_user_id; response.end(JSON.stringify(request.post.user);}); });

    Handling file uploads

    NSR handles 'multipart/form-data' out of the box. When NSR detects a post having enctype="multipart/form-data" it adds to the request object the properties: fileName, fileLen, fileData and fileType, which client code (your server) can handle as shown in the following usage example. This code can be seen in action invoked from upload example
    Usage:
    router.post("/handle_upload", function(request, response) { var encoding, fullname; response.writeHead(200, {'Content-type': 'text/html'}); if (request.fileName) { response.write("<h2>Uploaded File Data</h2&g"); response.write("File name = " + request.fileName + "<br/>"); response.write("File length = " + request.fileLen + " bytes<br/>"); response.write("File type = " + request.fileType + "<br/>"); fullname = "" + __dirname + "/public/uploads/" + request.fileName; if (request.fileType.indexOf('text') >= 0) { encoding = 'utf8'; } else { encoding = 'binary'; } return fs.writeFile(fullname, request.fileData, {encoding: encoding}, function(err) { if (err) { response.write("<p style='color: red;'>Something went wrong, uploaded file could not be saved.</p>"); } else { response.write('<div style="text-align:center; padding: 1em; border: 1px solid; border-radius: 5px;">'); if (request.fileType.indexOf('image') >= 0) { response.write("<img src='/uploads/" + request.fileName + "' />"); } else { response.write("<pre>" + request.fileData + "</pre>"); } response.write("</div>"); } response.write("<hr/>"); return response.end("<div style=\"text-align: center;\"><button onclick=\"history.back();\">Back</button></div>"); }); } else { response.write("<p style='color: red;'>Something went wrong, looks like nothing was uploaded.</p>"); return response.end("<div style=\"text-align: center;\"><button onclick=\"history.back();\">Back</button></div>"); } });
  • put

    Usage:
    router.put('/users', function(request, response) { updateUser(request.post.user, function(updated_user_id) { response.end(updated_user_id);}) });
  • patch

       A variant for PUT
    Usage:
    router.patch('/users', function(request, response) { updateUser(request.post.user, function(updated_user_id) { response.end(updated_user_id);}); });
  • delete

       
    Usage:
    router.delete('/users', function(request, response) { deleteUser(request.post.user_id, function(user_id) { response.end(user_id);}); });
  • any

       To be used when the request method is not known in advance. Sort of "catch all"
    Usage:
    router.any('/users', function(request, response) { response.end("User: " + getUserById(request.body.user_id).fullName);}); // Observe the usage of 'request.body' as the union of 'request.get' and 'request.post'
  • Complementary methods

    Up to here, all the enumerated methods are directly related to NSR primary activity: routing. They are what you will use 90% of the time.
    What follows are method loosely related to routing activity, but are the ones that give NSR some of its distinctiveness.
  • proxy_pass

       To deliver to the client the contents of an url from another server
    Usage:
    router.get('/whatismyip', function(request, response) { router.proxy_pass('http://testing.savos.ods.org/wimi', response);});
  • cgi

       To pass the client the results of an external CGI program.

    This one deserves an additional comment on its usefulness. While some - many perhaps - would argue that CGI doesn't make any sense from a Node.js development perspective, I still it's a worthy inclusion for a couple of reasons

    • First of all, you may have a legacy CGI module that you want/need to use in your brand new Node.js server - would you rewrite, for instance, Crafty, the chess engine, in Node? -
    • Writing programs that can talk to each other through standard means (stdin, stdout) has passed the test of time, and I think it has it niche even in the web server world.
    • If performance is a concern - and it should be - the present considerations still stand for the next item: SCGI, which NSR also supports. But there would not have been SCGI without CGI
    • Last but not least, CGI support makes the same sense in the context of a Node.js web server thant it does in Nginx, Apache, etc.. I'm not aware of anybody suggestiong CGI support should be dropped from any of them.

    Usage:
    By default, any static resource having a path that includes the router option 'cgi-dir' (which defaults to "cgi-bin") will be treated by NSR as a cgi program, provided the router option 'serve_cgi' is true.
    For example, the uri: /cgi-bin/hello.py will be handled as a CGI program.
    On the other hand, you can invoke directly the cgi method of the router, like so:
    router.cgi('/hidden-cgi-dir/mycgi.rb', request, response);
    Nevertheless, such way of using it is discouraged as it does not follow CGI standard guidelines.
  • scgi_pass

    To pass the client the results of an external program running under the SCGI protocol.

    Same considerations as those pertaining to CGI, with the added benefit of not having to spawn a new process each time.

    Why SCGI and not FCGI? Well, SCGI protocol was far easier to implement, and I really couldn't find significant performance differences between the two. FCGI may be implenented in future versions.

      Usage:
    //Example SCGI invocation. Output will be provided by a SCGI process listening on tcp port 26000. router.post("/scgi", function(request, response) { router.scgi_pass(26000, request, response); });
    The first parameter for scgi_pass is the port number (for tcp sockets) or the socket name (for unix sockets) at which the SCGI process is listening.

  • render_template

    To provide rudimentary template handling without compromising the goal of keeping NSR lean and simple.

    Even though templating is not directly related to routing, having a micro-templating utility was considered handy.
    It is basically a naive implementation of mustache.js, which tries to follow the spec, but at its current stage lacks partials and lambdas. Template handling as you would with any mustache template, as shown in the following example.

    Usage:
    router.get("/greet_user/:user_id", function(request, response) { get_user_by_id(request.params.user_id, function (user) { template_str = "<h2>Hello, {{ name }}!<h2>"; compiled_str = router.render_template(template_str, user); // returns "<h2>Hello, Joe Router!<h2>" response.end(compiled_str); } });
    Give it a test ride here

Added goodies

Really? Need more goodies?
Ok, here we go...

  • Default favicon If your app doesn't have a favicon, NSR provides one for you. I REALLY suggest you provide yours...
  • Default '404 - Not found' page. Once again, you're advised to provide your own.
  • Default '500 - Server Error' page. Same applies here.