Node.js itself is pretty bare bones. It's not a framework like Rails, but rather plain request-response handling. It's sort of like Python's Twisted framework, from what I gather.
There are more full-featured frameworks for node.js if you look around on github, but I'm bored and feel like committing the sin of writing yet more framework code.
This morning I started with a request router for Node.JS that leverages URI templates*. You specify the application as a series of request templates, paired with the functions that handle them.
For instance if you take the typical blogging application example, you might have a path like /posts/1234 - and the URI template would look like /posts/{postId}.
The magic is in turning {param}s in the URI template into parameters in the handler call.
Here's an example app that routes blog-like requests:
var handlers = {
'/posts/{postId}' : {
GET : function(postId) {
this.response.sendHeader(200, {"Content-Type": "text/plain"});
this.response.sendBody("GET Post ID: " + postId);
this.response.finish();
},
POST : function(postId) {
this.response.sendHeader(200, {"Content-Type": "text/plain"});
this.response.sendBody("POST Post ID: " + postId);
this.response.finish();
}
},
'/comments/{postId}/{commentId}' : {
GET : function(postId, commentId) {
this.response.sendHeader(200, {"Content-Type": "text/plain"});
this.response.sendBody("GET Post ID: " + postId + " Comment ID: " + commentId);
this.response.finish();
}
}
};As you can see the individual handler functions are further distinguished by HTTP method.
I'm not sure how to pass POST bodies to the handlers. They could just be attached to the handler's this I suppose.
Here's the full source:
var sys = require("sys"), http = require("http");
var handlers = {
'/posts/{postId}' : {
GET : function(postId) {
this.response.sendHeader(200, {"Content-Type": "text/plain"});
this.response.sendBody("GET Post ID: " + postId);
this.response.finish();
},
POST : function(postId) {
this.response.sendHeader(200, {"Content-Type": "text/plain"});
this.response.sendBody("POST Post ID: " + postId);
this.response.finish();
}
},
'/comments/{postId}/{commentId}' : {
GET : function(postId, commentId) {
this.response.sendHeader(200, {"Content-Type": "text/plain"});
this.response.sendBody("GET Post ID: " + postId + " Comment ID: " + commentId);
this.response.finish();
}
}
};
var Route = function(uriTemplate) {
this.uriTemplate = uriTemplate;
var nameMatcher = new RegExp('{([^}]+)}', 'g');
this.paramNames = this.uriTemplate.match(nameMatcher);
// the regex keeps the {} on the param names for some reason. TODO: fix this.
for (var i = 0; i < this.paramNames.length; i++) {
this.paramNames[i] = this.paramNames[i].replace('{', '').replace('}', '');
}
this.matcherRegex = this.uriTemplate.replace('?', "\\?").replace(/{([^}]+)}/g, '([^/?&]+)');
this.matcher = new RegExp(this.matcherRegex);
};
Route.prototype.parse = function(path) {
if (this.matcher.test(path)) {
var result = {};
var paramValues = this.matcher.exec(path);
// assert: paramValues.length == paramNames.length
for (var i = 1; i < paramValues.length; i++) {
result[this.paramNames[i-1]] = paramValues[i];
}
return result;
}
return null; //throw exception?
};
http.createServer(function (request, response) {
var handled = false;
for (pathTemplate in handlers) {
var route = new Route(pathTemplate);
var params = route.parse(request.uri.full);
if (params) {
// Convert the results to an array so we can pass them in via apply().
var values = [];
for (name in params) {
values[values.length] = params[name];
}
var handler = handlers[pathTemplate][request.method];
// So you can call this.request and this.response in the handlers.
handler.apply({'request' : request, 'response' : response}, values);
handled = true;
}
}
if (!handled) {
response.sendHeader(404, {"Content-Type": "text/plain"});
var output = "Couldn't route: " + request.uri.full + "\n";
for (name in request) {
output += name + ": " + request[name] + "\n";
}
response.sendBody(output);
response.finish();
}
}).listen(8000);
sys.puts("Server running at http://127.0.0.1:8000/");
The route lookup in the http.createServer could be a lot more efficient, like memoizing Route objects for instance. Anyways, NodeJS looks pretty exciting. Combined with CouchDB you could have a full JavaScript application stack: from storage to app server to client.
*Yes, I realize this is not a full implementation of the URI template spec. It's just a proof of concept.

4 comments: