{"id":22281385,"url":"https://github.com/sandy98/node-simple-router","last_synced_at":"2025-07-28T20:30:49.760Z","repository":{"id":3093303,"uuid":"4118092","full_name":"sandy98/node-simple-router","owner":"sandy98","description":"Yet another minimalistic router for node.js","archived":false,"fork":false,"pushed_at":"2022-07-21T04:06:39.000Z","size":3864,"stargazers_count":36,"open_issues_count":13,"forks_count":18,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-11-14T08:33:41.536Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"node-simple-router.herokuapp.com","language":"CoffeeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sandy98.png","metadata":{"files":{"readme":"README.md","changelog":"ChangeLog","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-04-23T21:25:01.000Z","updated_at":"2023-10-31T06:54:21.000Z","dependencies_parsed_at":"2022-09-10T20:41:10.247Z","dependency_job_id":null,"html_url":"https://github.com/sandy98/node-simple-router","commit_stats":{"total_commits":269,"total_committers":12,"mean_commits":"22.416666666666668","dds":0.06319702602230481,"last_synced_commit":"36ae2664095118116fa7ecae0545492065477846"},"previous_names":[],"tags_count":63,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandy98%2Fnode-simple-router","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandy98%2Fnode-simple-router/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandy98%2Fnode-simple-router/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandy98%2Fnode-simple-router/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sandy98","download_url":"https://codeload.github.com/sandy98/node-simple-router/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227952228,"owners_count":17846354,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-03T16:17:42.047Z","updated_at":"2024-12-03T16:17:42.802Z","avatar_url":"https://github.com/sandy98.png","language":"CoffeeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Node Simple Router \u003cimg src=\"https://raw.github.com/sandy98/node-simple-router/master/test/public/img/router50.png\" /\u003e\n### Yet another minimalistic router for node.js \n\n[![Known Vulnerabilities](https://snyk.io/test/github/sandy98/node-simple-router/badge.svg)](https://snyk.io/test/github/andy98/node-simple-router)\n\n# Getting started\n\n## \n**Step 1: Install**\n\nFrom Node Package Manager:\n\n```sh\nnpm install node-simple-router\n```\n\nFrom source:\n\n```sh\ngit clone https://github.com/sandy98/node-simple-router\n```\n\n\n## \n**Step 2: Test**\n\n`cd` to your installation directory and run `npm test`\nthen point your browser to _http://localhost:8000_ and review the info\nand above all, try the examples.\n\n\n## \n**Step 3: Run your server**\n\nYou can roll your own, or use the sample server that NSR provides by means of the mk-server utility:\n\n`mk-server js` will provide a barebones server (_server.js_)  with some example routes ready to run.\n\nIn order for this to work, you must have installed NSR global, like so:\n\n`sudo npm install -g node-simple-router`, or have the .bin directory of NSR in your path by whatever means you see fit.\n\nEither case, the basic steps are the same:\n\n#### Import 'http'\n\n```js\nvar http = require('http');\n```\n\n#### Import NSR\n\n```js\nvar Router = require('node-simple-router');\n```\n\n#### Instantiate the router\n\n```js\nvar router = Router(); // may also be router = new Router();\n```\n\n#### Add some routes\n\n```js\nrouter.get(\"/hello\", function(request, response) {response.end(\"Hello, World!\");});\n```\n\n#### Create an http server using router as the handler\n\n```js\nvar server = http.createServer(router);\n```\n\n#### Finally, make it listen on your chosen port and you're in business\n\n```js\nserver.listen(1234);\n```\n\n\n# Documents\n\n### Rationale\n\nRouting, in web app parlance, is about defining what code to execute when a given URL is invoked.\nNSR takes care of the necessary plumbing to make that happen,\nfreeing the programmer of the cumbersome details, allowing her to focus on the problem domain.\n#### How does it work?\nAs was stated in the lines above, it's not necessary to know \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e\ninner workings in order to be productive using it. Having said that, it is nevertheless useful to\nhave some insight on a couple of key aspects, fundamentally what could be called the \u003cem\u003e\"request wrapping\nmechanism\"\u003c/em\u003e.\n\nWhen you feed \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e with a url handling function, i.e. \n\n```js\nrouter.get(\"/answertoall\", function(request, response) {response.end(\"42\");});\n```\n\nwhat \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e\ndoes is to wrap that function into another, unnamed one, which has the primary mission of _\"augmenting\"_ the request\nobject and it stores said function in an array of url-handling functions, thus acting as a _middleware_ piece of code.\nAt 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.\n            \nWhat does _\"augmenting-dressing\"_  the request object mean?\n\nWell, basically, \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e provides the request object with 3 properties:\n\n* `request.get` which is an object representation of the \u003cdfn\u003equery string\u003c/dfn\u003e\n* `request.post` an object representation of what was posted, if anything\n* `request.body` is the union of the two previous items\n            \nIt should be pointed down that regardless the transmission method, \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e takes the necessary steps to make all 3 of them true javascript objects with all that implies, JSON and all.\n\nWorst case is an empty object **{}**, no errors.\n\nSo, 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\nthe request method. For example: \n\n```js\nrouter.any(\"/threefold\", function(request, response){response.end((parseInt(request.body.number) * 3).toString();});\n```\n\nWrapping up, you just got to remember `request.get`, `request.post` and `request.body`.\n             \nAnd that's all there is about it.\n\n### Options\n\nNSR sticks to some conventions (\"public\" as directory name for static assets, etc),\nwhich the programmer can override when instantiating the router, for instance:\n\n```js\nvar router = new Router({static_route: __dirname + \"/static\"});\n```\n    \nto change usage of the default \"public\" directory for static resources\n            \n\nList of default options:\n\n```js\nlogging: true\nlog: console.log\nstatic_route: \"#{process.cwd()}/public\"\nserve_static: true\nlist_dir: true\ndefault_home: ['index.html', 'index.htm', 'default.htm']\ncgi_dir: \"cgi-bin\"\nserve_cgi: true\nserve_php: true\nphp_cgi: \"php-cgi\"\nserved_by: 'Node Simple Router'\nsoftware_name: 'node-simple-router'\nadmin_user: 'admin'\nadmin_pwd: 'admin'\nuse_nsr_session: true\navail_nsr_session_handlers: ['dispatch.memory_store', 'dispatch.text_store']\nnsr_session_handler: 'dispatch.memory_store'\n```\n\nMost of them are self explanatory, but some deserve further comments, which will be added on doc completion.\n \n## Router API\n\nRouter object supports the following methods\n#### \u003cdfn\u003eget\u003c/dfn\u003e\n_Usage:_\n\n```js\nrouter.get('/users/:id', function(request, response) {\n    response.end(\"User: \" + getUserById(request.params.id).fullName);\n});\n```\n          \n#### \u003cdfn\u003epost\u003c/dfn\u003e\n_Usage:_\n\n```js\nrouter.post('/users', function(request, response) {\n    insertUser(request.post.user, function(new_user_id) {\n        request.post.user.id = new_user_id;\n        response.end(JSON.stringify(request.post.user);\n    });\n});\n```\n\n#### Handling file uploads\n\u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e handles 'multipart/form-data' out of the box.\nWhen \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e detects a post having enctype=\"multipart/form-data\" it\nadds to the _request_ object the properties: \u003cem\u003efileName, fileLen, fileData and\nfileType\u003c/em\u003e, which client code (your server) can handle as shown in the following usage example.\n\n_Usage:_\n\n```js\nrouter.post(\"/handle_upload\", function(request, response) {\n    var encoding, fullname;\n    response.writeHead(200, {'Content-type': 'text/html'});\n    if (request.fileName) {\n        response.write(\"\u0026lt;h2\u0026gt;Uploaded File Data\u0026lt;/h2\u0026g\");\n        response.write(\"File name = \" + request.fileName + \"\u0026lt;br/\u0026gt;\");\n        response.write(\"File length = \" + request.fileLen + \" bytes\u0026lt;br/\u0026gt;\");\n        response.write(\"File type = \" + request.fileType + \"\u0026lt;br/\u0026gt;\");\n        fullname = \"\" + __dirname + \"/public/uploads/\" + request.fileName;\n        if (request.fileType.indexOf('text') \u0026gt;= 0) {\n            encoding = 'utf8';\n        }\n        else {\n            encoding = 'binary';\n        }\n        return fs.writeFile(fullname, request.fileData, {encoding: encoding}, function(err) {\n            if (err) {\n                response.write(\"\u0026lt;p style='color: red;'\u0026gt;Something went wrong, uploaded file could not be saved.\u0026lt;/p\u0026gt;\");\n            }\n            else {\n                response.write('\u0026lt;div style=\"text-align:center; padding: 1em; border: 1px solid; border-radius: 5px;\"\u0026gt;');\n                if (request.fileType.indexOf('image') \u0026gt;= 0) {\n                    response.write(\"\u0026lt;img src='/uploads/\" + request.fileName + \"' /\u0026gt;\");\n                }\n                else {\n                    response.write(\"\u0026lt;pre\u0026gt;\" + request.fileData + \"\u0026lt;/pre\u0026gt;\");\n                }\n                response.write(\"\u0026lt;/div\u0026gt;\");\n            }\n            response.write(\"\u0026lt;hr/\u0026gt;\");\n            return response.end(\"\u0026lt;div style=\\\"text-align: center;\\\"\u0026gt;\u0026lt;button onclick=\\\"history.back();\\\"\u0026gt;Back\u0026lt;/button\u0026gt;\u0026lt;/div\u0026gt;\");\n           });\n         }\n         else {\n           response.write(\"\u0026lt;p style='color: red;'\u0026gt;Something went wrong, looks like nothing was uploaded.\u0026lt;/p\u0026gt;\");\n           return response.end(\"\u0026lt;div style=\\\"text-align: center;\\\"\u0026gt;\u0026lt;button onclick=\\\"history.back();\\\"\u0026gt;Back\u0026lt;/button\u0026gt;\u0026lt;/div\u0026gt;\");\n         }\n      });\n```\n      \n#### \u003cdfn\u003eput\u003c/dfn\u003e\n_Usage:_\n\n```js\nrouter.put('/users', function(request, response) {\n    updateUser(request.post.user, function(updated_user_id) {\n    response.end(updated_user_id);})\n});\n```\n\n```js\nuse_nsr_session: true\navail_nsr_session_handlers: ['dispatch.memory_store', 'dispatch.text_store']\nnsr_session_handler: 'dispatch.memory_store'\n```\n\n\n#### \u003cdfn\u003epatch\u003c/dfn\u003e\nA variant for PUT\n\n_Usage:_\n\n```js\nrouter.patch('/users', function(request, response) {\n    updateUser(request.post.user, function(updated_user_id) {\n    response.end(updated_user_id);});\n});\n```\n\n#### \u003cdfn\u003edelete\u003c/dfn\u003e\n\n_Usage:_\n\n```js\nrouter.delete('/users', function(request, response) {\n    deleteUser(request.post.user_id, function(user_id) {\n    response.end(user_id);});\n});\n```\n\n#### \u003cdfn\u003eany\u003c/dfn\u003e\nTo be used when the request method is not known in advance. Sort of \"catch all\"\n\n_Usage:_\n\n```js\n// Observe usage of 'request.body' as the union of 'request.get' and 'request.post'\nrouter.any('/users', function(request, response) {\n    response.end(\"User: \" + getUserById(request.body.user_id).fullName);\n}); \n```\n\n### \u003cdfn\u003eComplementary methods\u003c/dfn\u003e\n \nUp to here, all the enumerated methods are directly related to \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e primary activity: routing.\n\nThey are what you will use 90% of the time.\n\nWhat follows are method loosely related to routing activity, but are the ones that give \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e some of its distinctiveness.\n\n#### \u003cdfn\u003eproxy_pass\u003c/dfn\u003e\n\nTo deliver to the client the contents of an url from another server\n\n_Usage:_\n\n```js\nrouter.get('/whatismyip', function(request, response) {\n    router.proxy_pass('http://testing.savos.ods.org/wimi', response);\n});\n```\n\n#### \u003cdfn\u003e\u003cabbr title=\"Common Gateway Interface\"\u003ecgi\u003c/abbr\u003e\u003c/dfn\u003e\n\nTo pass the client the results of an external CGI program.\n\nThis 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\n - 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?\n - 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.\n - 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\n - 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.\n\n_Usage:_\n\n\u003csamp\u003e\n    By default, any static resource having a path that includes the router option 'cgi-dir'\n    (which defaults to \"cgi-bin\") will be treated by \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e\n    as a cgi program, provided the router option 'serve_cgi' is true.\n    For example, the uri: `/cgi-bin/hello.py` will be handled as a CGI program.\n    On the other hand, you can invoke directly the cgi method of the router, like so:\n    `router.cgi('/hidden-cgi-dir/mycgi.rb', request, response);`\n    Nevertheless, such way of using it is discouraged as it does not follow CGI standard\n    guidelines.\n\u003c/samp\u003e\n\n#### \u003cdfn\u003escgi_pass\u003c/dfn\u003e\n                            \n\nTo pass the client the results of an external program running under the [SCGI](http://en.wikipedia.org/wiki/SCGI) protocol.\n\nSame considerations as those pertaining to CGI, with the added benefit of not having to spawn a new process each time.\n\nWhy SCGI and not \u003cdfn title=\"Fast CGI\"\u003eFCGI\u003c/dfn\u003e? 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.\n\n_Usage:_\n\n```js\n//Example SCGI invocation. Output will be provided by a SCGI process listening on tcp port 26000.\nrouter.post(\"/scgi\", function(request, response) {\n  router.scgi_pass(26000, request, response);\n});\n```\n\nThe first parameter for scgi_pass is the port number (for tcp sockets)\nor the socket name (for unix sockets) at which the SCGI process is listening.\n\n#### \u003cdfn\u003erender_template\u003c/dfn\u003e\n                            \n\nTo provide rudimentary template handling without compromising the goal of keeping \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e lean and simple.\n\nEven though templating is not directly related to routing, having a micro-templating utility was considered handy.\n\nIt is basically a naive implementation of [mustache.js](http://mustache.github.io/), which tries to follow the [spec](http://mustache.github.io/mustache.5.html), but at its current stage lacks partials and lambdas. Template handling as you would with any mustache template, as shown in the following example.\n\n_Usage:_\n\n```js\nrouter.get(\"/greet_user/:user_id\", function(request, response) {\n    get_user_by_id(request.params.user_id, function (user) {\n        template_str = \"\u0026lt;h2\u0026gt;Hello, {{ name }}!\u0026lt;h2\u0026gt;\";\n        compiled_str = router.render_template(template_str, user); // returns \"\u0026lt;h2\u0026gt;Hello, Joe Router!\u0026lt;h2\u0026gt;\"\n        response.end(compiled_str);\n    }\n});\n```\n\nNew in version 0.8.8: `render_template_file(fileName, context, callback, keep_tokens)` was added\nThe method signature is almost the same than for **render_template** with 2 differences worth noting\n\n* The first parameter is a string containing the file name, not the template string itself.\n\n* There is a new parameter, previous to last one: a callback function with the following signature:\n`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.\n                                \nObviously it would have been better to keep only one method (**render_template**), letting it guess if the first parameter is  a file name or a template string, but doing it that way would introduce a backward incompatibility since **render_template** returns a string, while becoming async would make it need a callback. So... too late to regret. It's not big deal, anyway..\n\n\n#### \u003cdfn\u003eSession handling\u003c/dfn\u003e\n\n\nThis section deals with session handling utilities built in with NSR.\n\n\u003cp\u003e\n    \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e augments the request object with a \u003cem\u003ensr_session\u003c/em\u003e object\n    unless the option \u003cem\u003euse_nsr_session\u003c/em\u003e is set to a falsy value (defaults to true).\u003cbr/\u003e\n    \u003cstrong\u003e\u003cem\u003ensr_session\u003c/em\u003e\u003c/strong\u003e is a javascript object that holds the session keys and values\n    defined by the application. Incidentally, the reason \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e uses\n    \u003cem\u003erequest.nsr_session\u003c/em\u003e and not \u003cem\u003erequest.session\u003c/em\u003e is to avoid name collision in case\n    that a separate session handling mechanism is used.\n\u003c/p\u003e\n\n\u003cp\u003e\n    Options related to session handling:\n    \u003cul style=\"list-style-type: none;\"\u003e\n       \u003cli\u003e\u003cstrong\u003euse_nsr_session\u003c/strong\u003e Set \u003cem\u003ensr_session\u003c/em\u003e on or off\u003c/li\u003e\n       \u003cli\u003e\u003cstrong\u003eavail_nsr_session_handlers\u003c/strong\u003e Low level functions that perform the real action. See below for details\u003c/li\u003e\n       \u003cli\u003e\u003cstrong\u003ensr_session_handler\u003c/strong\u003e The one handler currently selected\u003c/li\u003e\n    \u003c/ul\u003e\n\u003c/p\u003e\n\n\u003cp\u003e\n    Methods related to session handling:\n    \u003cul style=\"list-style-type: none;\"\u003e\n        \u003cli\u003e\u003ccode\u003eaddSessionHandler(function, function_name)\u003c/code\u003e Add your own function to the list of session handlers\u003c/li\u003e\n        \u003cli style=\"margin-bottom: 1em;\"\u003e\u003ccode\u003esetSessionHandler(func_name_or_ordinal)\u003c/code\u003e Tell \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e which of the available handlers to use (defaults to 0, 'memory_store').\u003c/li\u003e\n        \u003cli\u003e\u003ccode\u003egetSession(request, callback)\u003c/code\u003e Get the current \u003cem\u003erequest.nsr_session\u003c/em\u003e.\u003c/li\u003e\n        \u003cli\u003e\u003ccode\u003esetSession(request, session_object, callback)\u003c/code\u003e Set the current \u003cem\u003erequest.nsr_session\u003c/em\u003e with the provided session_object.\u003c/li\u003e\n        \u003cli\u003e\u003ccode\u003eupdateSession(request, session_object, callback)\u003c/code\u003e Update the current \u003cem\u003erequest.nsr_session\u003c/em\u003e with the provided session_object, keeping not included keys and adding-updating keys present in session_object.\u003c/li\u003e\n    \u003c/ul\u003e\n    \u003cdiv style=\"margin-top: 1em;\"\u003e\n        The three latter methods are convenience wrappers to access the low level method that handles \u003cem\u003ensr_session\u003c/em\u003e\n        which is responsible for the real implementation of the session mechanism.\u003cbr/\u003e\n        The 3 are asynchronous, having the \u003cem\u003ensr_session\u003c/em\u003e returned as the only argument to the callback function.\u003cbr/\u003e\n        Two low level handlers are provided built in: \u003cem\u003ememory_store\u003c/em\u003e (default) and \u003cem\u003etext_store\u003c/em\u003e (serializes each session to a file named by the session ID)\u003cbr/\u003e\n        If you don't need/want to construct your own implementation, you don't need to know a thing\n        about it, but if you want to roll your own (for instance, if you want to save sessions to a database or a remote server)\n        here are a couple of things that you should keey in mind:\n    \u003c/div\u003e\n    \u003cdiv style=\"margin-top: 1em;\"\u003e\n      \u003cspan style=\"1em; padding-left: 3em;\"\u003eFunction signature: \u003ccode\u003evar db_store = function(request, opcode, sess_obj, callback);\u003c/code\u003e\u003c/span\u003e\u003cbr/\u003e\n      \u003cspan style=\"1em; padding-left: 3em;\"\u003eHaving a look at the source code used in \u003cem\u003ememory_store\u003c/em\u003e and \u003cem\u003etext_store\u003c/em\u003e should provide a fairly good idea of how things should work\u003c/span\u003e\u003cbr/\u003e\n      \u003cspan style=\"1em; padding-left: 3em;\"\u003eIn order to put your brand new session handler to work, you have to\u003c/span\u003e\u003cbr/\u003e\n      \u003cspan style=\"1em; padding-left: 4em;\"\u003e1) Register it with \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e by means of \u003cem\u003eaddSessionHandler\u003c/em\u003e\u003c/span\u003e\u003cbr/\u003e\n      \u003cspan style=\"1em; padding-left: 4em;\"\u003e2) Put it to use with \u003cem\u003esetSessionHandler\u003c/em\u003e\u003c/span\u003e\u003cbr/\u003e\n    \u003c/div\u003e\n\u003c/p\u003e\n\n\u003cp\u003e\n    You can see the session machinery in action in the \u003ca href=\"http://node-simple-router.herokuapp.com/session\"\u003eSession Handling\u003c/a\u003e section of the demo site.\u003cbr/\u003e\n    By all means, review the code that makes it work in \u003cem\u003etest/server.js\u003c/em\u003e or \u003cem\u003etest/server.coffee\u003c/em\u003e if you are so inclined.\n\u003c/p\u003e\n\n\u003cdiv class=\"panel panel-primary\"\u003e\n    \u003cdiv class=\"panel-heading\"\u003e\n        \u003ch3 class=\"panel-title\"\u003eUtilities\u003c/h3\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"panel-body\"\u003e\n        \u003cp\u003e\n            There are a bunch of utilities that can make your job easier, such as mk-server, the standalone tool\n            that will generate a \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e driven web server ready to go, cookie handling, uuid\n            generation, built-in async or promises (flow-control routines), but these are - rather, will be - fully commented in\n            \u003ca target=\"_blank\" href=\"https://github.com/sandy98/node-simple-router/wiki/Utils\"\u003eutilities section of\n            NSR wiki\u003c/a\u003e\n        \u003c/p\u003e\n    \u003c/div\u003e\n\u003c/div\u003e\n\n### Real time\n\nBeginning with v0.9.0 NSR becomes real time. Now not only http requests routing is enabled, \nbut also the **ws** protocol (WebSockets) is implemented.\n\nSee all the juicy details at \u003ca target=\"_blank\" href=\"https://github.com/sandy98/node-simple-router/wiki/WebSocket\"\u003eWebSocket section of\n            NSR wiki\u003c/a\u003e or see it in action \u003ca href=\"http://node-simple-router.herokuapp.com/sillychat.html\"\u003eat the demo site.\u003c/a\u003e\n\n### Added goodies\n\nReally? Need more goodies?\n\nOk, here we go...\n -  **Default favicon** If your app doesn't have a favicon, \u003cspan class=\"nsr\"\u003eNSR\u003c/span\u003e provides one for you. I _REALLY_ suggest you provide yours...\n -  **Default '404 - Not found' page.** Once again, you're advised to provide your own.\n -  **Default '500 - Server Error' page.** Same applies here.\n\n## License\n\n(The MIT License)\n\nCopyright (c) 2012 Ernesto Savoretti \u003cesavoretti@gmail.com\u003e\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsandy98%2Fnode-simple-router","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsandy98%2Fnode-simple-router","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsandy98%2Fnode-simple-router/lists"}