NodeJS Http Server

const http = require('http');

The Nodejs provides the core Http server apis in its module-http as shown above. There are many other web application libraries like express, koa, happi, axios which are built on these apis and provide much more convenient apis.

Here in this article we will look at creating simple web servers to understand the usage and limitation of these core modules in building web applications.

Basic Web Server

Here is a basic structure of our web applications in Nodejs to accept a request and send out a response.

As shown in the diagram we will discuss on web application apis in three parts :

  1. Create the Server
  2. Handle the Request : Available apis to extract details from the request objects
  3. Send the Response : Using different status code and data formats in the response

1. Create the Server

Let us start with simple hello world server.

const http = require('http');

const hostname = '127.0.0.1';
const port = 8080;

//Part-1a: Create a Http server that can accept the request and response
const server = http.createServer((req, res) => {

  //Part-2: We are yet to add anything here

  //part-3: This is a fixed response for all request
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello, World!\n');

});

//Part-1b: Specify the hostname and port on which the server has to listen
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

As highlighted, the part1.a creates the server which on each request executes the callback function with the http request and response as the parameters as shown.

The part1.b specifies the port and hostname to listen to for the incoming requests. In case we do not specify the host name, it will take the localhost as the default.

Since we are not handling the requests here, every request is going to send the same response as plain text as shown.

We can run this server using node command as below. The next step shows the response of the server using curl. Off course we can also check it on a browser.

F:\nodejs\http>node http-server.js
Server running at http://127.0.0.1:8080/ 
F:\nodejs>curl http://127.0.0.1:8080/
Hello, World!

 

2. Handle Request

This part includes identifying the purpose of the request, so that we can handle their processing accordingly. We can get it from url path, url parameters, header parameters, request methods(GET, POST, PUT, DELETE), request body etc.

Getting the information from headers, url or the request method is straight forward, but we need little bit of code to extract the request data from body in the POST or PUT requests. Hence, we would be looking at these two cases separately here.

 

Case -1: For GET Requests

In this regard getting the url, method or the header parameters is easy as Nodejs makes them available as different properties in the request object as shown:

const { headers , method, url } = request;

But, for parsing the url for path and parameters we can use the url module as follows:

var url = require('url');

// We can get the input for url.parse from request.url
let  myUrl = url.parse('/myapp/book?id=123',true);

console.log("pathname : "+ myUrl.pathname);  // pathname : /myapp/book
console.log("search : "  + myUrl.search);    // search : ?id=123

console.log("query : " + JSON.stringify(myUrl.query));  // query : {"id":"123"}
console.log("query.id : "+ myUrl.query.id);             // query.id : 123


myUrl = url.parse('/myapp/book',true);
console.log("query : "  + JSON.stringify(myUrl.query));  // query : {}
console.log("query.id : " + myUrl.query.id);             // query.id : undefined

If we are having only the GET requests, we can easily differentiate those pages using the above apis. Below is an example of that.

const http = require('http');
const url = require('url');

const server = http.createServer((req, res) => {
    const pathname = url.parse(req.url,true).pathname;
    const query    = url.parse(req.url,true).query;

     if('/'==pathname && req.method === 'GET'){

       res.statusCode = 200;
       res.setHeader('Content-Type', 'text/html');
       res.write('<html><body><h1> Awesome Furnitures Home Page </h1></body></html>');
       res.end();

     } else if('/products'==pathname && req.method === 'GET' && query.category != null){

       res.statusCode = 200;
       res.setHeader('Content-Type', 'text/html');
       res.write('<html><body><h1> Products from Category :'+ query.category + '</h1></body></html>');
       res.end();

     } else if('/products'==pathname && req.method === 'GET'){

       res.statusCode = 200;
       res.setHeader('Content-Type', 'text/html');
       res.write('<html><body><h1> Listing All Product Categories </h1></body></html>');
       res.end();

     } else {

        res.statusCode = 404;
        res.setHeader('Content-Type', 'text/html');
        res.write('<html><body><h1> Page NOT Found </h1></body></html>');
        res.end();
    }


});

server.listen(8080, () => {
  console.log(`Server running at http://localhost:8080/`);
});

The paths could be for REST services or simple web pages, but for simplicity we are using dummy htmls as our outcome here.

For each page we are setting the status code, header , content in the response and then finally sending it by using res.end().

Now we can run the program using the following command :

F:\nodejs\http>node http-server-routing.js
Server running at http://localhost:8080/

We can check these pages on a browser and here are the corresponding responses from curl requests :

F:\nodejs>curl http://localhost:8080
<html><body><h1> Awesome Furnitures Home Page </h1></body></html>

F:\nodejs>curl http://localhost:8080/products
<html><body><h1> Listing All Product Categories </h1></body></html>

F:\nodejs>curl http://localhost:8080/products?category='bed'
<html><body><h1> Products from Category :'bed'</h1></body></html>

F:\nodejs>curl http://localhost:8080/anyhingelse
<html><body><h1> Page NOT Found </h1></body></html>

 

Case-2 : For POST and PUT Requests

For POST and PUT requests getting the body using core apis is not straight forward. Here, we can make use of the different events emitted by the request object which implements a ReadableStream.

      let data = '';
      req.setEncoding('utf8');
    
      //Things to do on error while working on request
      req.on('error', (err) =>{
          console.log(err);
          //Bad request
          response.statusCode = 400;
          res.write(JSON.stringify({message : 'Something went wrong !'}));
          response.end();
      });

      //Collect the data coming in chunks
      req.on('data', (chunk) => data += chunk);

       //At the end of request processing work on the resultant data
      req.on('end', () => {
            console.log(`Validate & Save : ${data}`);

            res.statusCode = 200;
            res.setHeader('Content-Type', 'text/json');
            res.write(JSON.stringify(data));
            res.end();
       });

The ‘data‘ events help us capture the streaming data, even if it arrives in multiple chunks. The ‘error‘ event allows us to handle error while processing requests.

Finally, by the time we get the ‘end‘ event, we are ready with our data for further processing like validating , saving and sending the response. Hence, as we can see, we have kept the response for the request within this event.

With the above events in place, a POST service will look as below:

 

const http = require('http');
const url = require('url');


const server = http.createServer((req, res) => {
    const pathname = url.parse(req.url).pathname;

    //Things to do on error while working on request
    req.on('error', (err) =>{
          console.log(err);
          //Bad request
          response.statusCode = 400;
          res.write(JSON.stringify({message : 'Something went wrong !'}));
          response.end();
    });

    if('/products'==pathname && req.method === 'POST'){

      let data = '';
      req.setEncoding('utf8');

      //Collect the data coming in chunks
      req.on('data', (chunk) => data += chunk);

       //At the end of request processing work on the resultant data
      req.on('end', () => {
            console.log(`Validate & Save : ${data}`);

            res.statusCode = 200;
            res.setHeader('Content-Type', 'text/json');
            res.write(JSON.stringify(data));
            res.end();
       });

    }else {

       res.statusCode = 404;
       res.setHeader('Content-Type', 'text/html');
       res.write(JSON.stringify({message : 'Invalid request !'}));
       res.end();

   }
});

server.listen(8080, () => {
  console.log(`Server running at http://localhost:8080/`);
});

Here, we have moved ‘error’ event on the request outside to make it common for all POST or PUT requests.

Now let us run the server and verify the post request using Postman.

F:\nodejs\http>node http-server-post.js
Server running at http://localhost:8080/
POST request on Postman

Here, we are successfully reading the data from the request body and simply sending the same in the response.

 

3. Send Response

As we have seen above we can send different formats of data in the response. The basic contents of the response are the status code, content-type , the content.

            
            response.statusCode = 200;
            response.setHeader('Content-Type', 'text/json');

            //We can use response.write multiple times
            response.write(JSON.stringify(data));

            //Unless we call this, the Response will not be sent
            response.end();

Like in request we can also commonly handle any error in response using it’s error event.

  response.on('error', (err) => {
    console.error(err);
  });

Below are some of the important response status code

response.statusCode = 200; // Successful response

response.statusCode = 400; // Bad Request
response.statusCode = 403; // Forbidden Resource
response.statusCode = 404; // Resource not found

response.statusCode = 500; // Internal Server Error

 

Conclusion

As we saw, even for creating a simple routing as above, we had to implement all the routing mechanism manually. Similarly, it will also require a lot of effort in organizing modules, creating proper error handling mechanism and so on.

Luckily we have so many open source libraries such as Express, Koa, Meteor, Hapi etc which make all these easy and configurable. Since, all of these are built on these core APIs directly or indirectly, it good to know how these core APIs work. But, to build our real-time business applications, its always recommended to use one of those advanced libraries.