Routing the requests

Almost any web application serves more than a single resource. So now we know how to serve content using an HTTP server; but how do we handle multiple resources? Routing is the name of the game. We need to understand the incoming request and map it to the appropriate request handler. This is a bit more complicated than the previous example, so we will build it step by step, improving it with every step.

To demonstrate the routing of requests, let us build a simple application that serves two resources at /start and /finish, displaying Hello and Goodbye respectively. To simplify the code, we will serve plain text. So before anything else, let's take a look at the code:

var http = require("http");
var url = require("url");

function onRequest(request, response) {
  var pathname = url.parse(request.url).pathname;
  console.log("Request for " + pathname + " received.");
  if(pathname === "/start"){
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Hello");
    response.end();
  }else if(pathname === "/finish"){
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Goodbye");
    response.end();
  }else{
    response.writeHead(404, {"Content-Type": "text/plain"});
    response.end("404 Not Found");
  }
}

http.createServer(onRequest).listen(9999);
console.log("Server has started.");

Save the previous code snippet in a file called routing.js and execute it as follows:

node routing.js

Now, when we access http://localhost:9999/start, we will see Hello in the browser. Similarly, when we access http://localhost:9999/finish, we will see a message saying Goodbye. If we try to access any other path, we will get an HTTP 404 or Not Found error. Let us now try and understand the new things we are introducing in this example.

The first thing that we need in order to route a request, is to parse the URL; for this we will introduce another inbuilt module called url. When a URL string is parsed using the url module, it returns an instance of the URL. In this case, we are interested in the pathname.

var pathname = url.parse(request.url).pathname;

In the previous line of code, we are passing the url string from the request, and parsing it using the url module, to get the pathname. The next step is to send an appropriate response, based on the path being accessed:

  if(pathname === "/start"){
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Hello");
    response.end();
  }else if(pathname === "/finish"){
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Goodbye");
    response.end();
  }

Here we are comparing the pathname with the one we are expected to handle, and accordingly an appropriate response is sent out. And what happens to the requests we don't handle? That is what the last part of the if-else-if ladder does. It sends an HTTP 404 error.

    response.writeHead(404, {"Content-Type": "text/plain"});
    response.end("404 Not Found");

Now let us think about extending this application. To handle more paths, we will have to add more if-else conditions. But that doesn't look clean, is difficult to read, and is very inefficient in execution. Think about the route handled in the last step of the if-else ladder; the process still has to go through the entire ladder, checking for every condition. Also, adding new routes to this will require us to go through and edit this if-else ladder, which will be at the very least, confusing, and can also easily result in errors, typos, and a high chance of unintentional modification to the existing routes. So let us make it a bit cleaner by putting the handlers in an object mapped by their paths, and also provide an API to extend it. So let us change our code to look like this:

var http = require("http");
var url = require("url");

var route = { 
  routes : {}, 
  for: function(path, handler){
    this.routes[path] = handler;
  }
};

  route.for("/start", function(request, response){
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Hello");
    response.end();
  });

  route.for("/finish", function(request, response){
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Goodbye");
    response.end();
  });

function onRequest(request, response) {
  var pathname = url.parse(request.url).pathname;
  console.log("Request for " + pathname + " received.");
  if(typeof route.routes[pathname] ==='function'){
    route.routes[pathname](request, response);
  }else{
    response.writeHead(404, {"Content-Type": "text/plain"});
    response.end("404 Not Found");
  }
}

http.createServer(onRequest).listen(9999);
console.log("Server has started.");

To run this code snippet, execute the file with Node.js, using the following command:

node resources.js

The functionality of the application will be the same as the result of the previous example. When we access either /start or /finish, it will respond with Hello for the former, and Goodbye for the latter. On trying to access any other path, we will get an HTTP 404 message.

The change we have made here is that we have thrown out the if-else-if ladder in favor of a clean and efficient design approach. In this approach, we don't need to play around with existing routes and can add new routes by calling the route.for method from any module. The route has a map of the path to the handler function and also has a on method to add new routes.

var route = {
  routes : {},
  for: function(path, handler){
    this.routes[path] = handler;
  }
}

route.on("/start", function(request, response){
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Hello");
    response.end();
});

route.on("/finish", function(request, response){
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Goodbye");
    response.end();
});

Here we are adding two new handlers for the paths /start and /finish. The signature for the handlers is similar to the main request handler. We expect the handlers to get the request and response, so that the handler has everything it needs to process the request and send the response.

if(typeof(route.routes[pathname])==='function')

In the if condition, we check whether the route for the pathname is present, and whether it is a function. If we find a handler for the requested path, we execute the handler function, passing the request and response to it.

route.routes[pathname](request, response);

If it is not found, we respond with an HTTP 404 error. Now, to add a new path, we can call the route.on method with the path and its handler to register it.

route.on("/newpath", function(request, response){ 
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("new response");
    response.end();
});
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset