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.
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.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.
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'
use_nsr_session: true
avail_nsr_session_handlers: ['dispatch.memory_store', 'dispatch.text_store']
nsr_session_handler: 'dispatch.memory_store'
Most of them are self explanatory, but some deserve further comments, which will be added on doc completion.
Router object supports the following methods
Usage:router.get('/users/:id', function(request, response) { response.end("User: " + getUserById(request.params.id).fullName);});
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);}); });
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>"); } });
Usage:router.put('/users', function(request, response) { updateUser(request.post.user, function(updated_user_id) { response.end(updated_user_id);}) });
Usage:router.patch('/users', function(request, response) { updateUser(request.post.user, function(updated_user_id) { response.end(updated_user_id);}); });
Usage:router.delete('/users', function(request, response) { deleteUser(request.post.user_id, function(user_id) { response.end(user_id);}); });
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'
Usage:router.get('/whatismyip', function(request, response) { router.proxy_pass('http://testing.savos.ods.org/wimi', response);});
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
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.
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.
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:Give it a test ride here
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); } });
New in version 0.8.8: render_template_file(fileName, context, callback, keep_tokens)
was added
The method signature is almost the same than for render_template with 2 differences worth noting
cb(exists, rendered_text);
. The reason for this is that obviously file retrieval is an async IO
operation and as such it needs a callback, which render_template doesn't need as there is no IO involved.
This section deals with session handling utilities built in with NSR.
NSR augments the request object with a nsr_session object
unless the option use_nsr_session is set to a falsy value (defaults to true).
nsr_session is a javascript object that holds the session keys and values
defined by the application. Incidentally, the reason NSR uses
request.nsr_session and not request.session is to avoid name collision in case
that a separate session handling mechanism is used.
Options related to session handling:
Methods related to session handling:
addSessionHandler(function, function_name)
Add your own function to the list of session handlerssetSessionHandler(func_name_or_ordinal)
Tell NSR which of the available handlers to use (defaults to 0, 'memory_store').getSession(request, callback)
Get the current request.nsr_session.setSession(request, session_object, callback)
Set the current request.nsr_session with the provided session_object.updateSession(request, session_object, callback)
Update the current request.nsr_session with the provided session_object, keeping not included keys and adding-updating keys present in session_object.var db_store = function(request, opcode, sess_obj, callback);
You can see the session machinery in action in the Session Handling section of this demo site.
By all means, review the code that makes it work in test/server.js or test/server.coffee if you are so inclined.
There are a bunch of utilities that can make your job easier, such as mk-server, the standalone tool that will generate a NSR driven web server ready to go, cookie handling, uuid generation, built-in async and promises (flow-control routines), but these are - rather, will be - fully commented in utilities section of NSR wiki
Beginning with v0.9.0 NSR becomes real time. Now not only http requests routing is enabled,
but also the ws protocol (WebSockets) is implemented.
See all the juicy details at WebSocket section of
NSR wiki or see it in action at the demo site.
Really? Need more goodies?
Ok, here we go...