How to write basic web/HTTP server using pure NodeJS API

backend5 Min to Read10 Aug 17

In many programming languages, we don't write our own server, instead, we write just routing and business logic & deploy it using independent, external, standalone server (e.g tomcat, apache etc). But in Node.JS, we have the privilege to write our own server, it's lightweight, slim, & super fast.

I have seen many people that when they start learning Node.JS, they use a framework like express.js, Restify, Koa.js etc. to create and manage HTTP server. It is ok to learn and use such framework because they give us a nice, easy, and manageable interface to create HTTP server by hiding pure, raw and not-so-good-looking API of Node.JS.

But it is also important to learn and understand on how to create HTTP server using Node.JS pure, raw API. All the framework internally uses these Node.JS raw HTTP API to create their own interface. So in this article, we will learn how to create simple HTTP server using pure Node.JS API.

Creating http server

Any server has mainly two work as mentioned below.

  1. Keep listening for client request on some port
  2. Once client request comes, handle it & send proper response to client.

We will create our simple http server in below three steps.

  1. create a http server
  2. listen/bind http server to some port
  3. provide requestResponse handler to http server

Step1: create

http is the main module in Node.JS that provide all the API to create and manage HTTP server. So we will first import this module using const http = require('http') then we will use function createServer of http module. This function takes a callback function as a parameter and returns a server object.

As you can see in below code, we are passing requestResponseHandler as an argument. requestResponseHandler is a function that we have not defined yet, will do it in Step3. Node.JS will automatically call this function whenever any new request comes. So our all the logic to handle the request and sending proper response should be written inside this function. we will see all these logic in step3.

const http = require("http")
const httpServer = http.createServer(requestResponseHandler)

Stpe2: listen

Now we have created our HTTP server and stored it in a variable httpServer, now let's listen/bind this server to some port (eg 3000); We do this using httpServer's method listen, this method takes port and callback as argument.

httpServer.listen(3000, () => {
  console.log("Node.JS static file server is listening on port 3000")
})

Step3: handleRequest

So far, we have created our server and it is listening on port 3000 but it will not be able to serve the response to client request because we have not yet defined our request/response handler. Let's define it.

As we said earlier, requestResponseHandler is a function that is called by Node.JS whenever any client request comes to server. So all the logic to interpret client request and serve a proper response to the client should be written inside this function. What kind of client request will come?

Client will ask for file like 'index.html', 'app.css', 'app.js' etc. in fact it can ask for any file, we just have to serve that file to the client with proper MIME TYPE also called Content-Type. Why we are sending Content-Type, why can't we just send the file content? to help browser render the content properly.

Let's take an example, suppose our server is sending 'inext.html' with 'Content-type=text/plain' instead of 'Content-Type=text/html', then browser will think its normal text file, not html file. So browser might decide not to render html page instead show the source code of 'index.html' as normal text, which we don't want.

Enough talk! lets see how to do this in below code.

const http = require("http");
const fs = require("fs");
const path = require("path");

function requestResponseHandler(request, response) {
  console.log(`Request came: ${req.url}`);
  if (request.url === "/") {
    sendResponse("index.html", "text/html", response);
  } else {
    sendResponse(request.url, getContentType(request.url), response);
  }
}

function sendResponse(url, contentType, res) {
  let file = path.join(\_\_\_dirname, url);
  fs.readFile(file, (err, content) => {
    if (err) {
      res.writeHead(404);
      res.write(`File '${file}' Not Found!`);
      res.end();
      console.log("Response: 404 ${file}, err");
    } else {
      res.writeHead(200, { "Content-Type": contentType });
      res.write(content);
      res.end();
      console.log(`Response: 200 ${file}`);
    }
  });
}

function getContentType(url) {
  switch (path.extname(url)) {
    case ".html":
      return "text/html";
    case ".css":
      return "text/css";
    case ".js":
      return "text/javascript";
    case ".json":
      return "application/json";
    default:
      return "application/octate-stream";
  }
}

As you can see the definition of our requestResponseHandler, whenever client request comes, Node.JS will call this function with two arguments namely request and response object. We have created separate function sendResponse and getContentType instead of putting all code inside one single function requestResponseHandler for code readablity. Depending on the request URL, we are reading the content of the file using fs.readFile().

To send http response code and set various response header, we use res.writeHead(200, {'Content-Type': contentType});. To send the actual content we use res.write(content). request and response are stream in Node.JS, so once we finish sending response, just close the response stream using res.end().

Full code

const http = require("http");
const fs = require("fs");
const path = require("path");

const httpServer = http.createServer(requestResponseHandler);
httpServer.listen(3000, () => {
  console.log("Node.JS static file server is listening on port 3000");
});
function requestResponseHandler(request, response) {
  console.log(`Request came: ${req.url}`);
  if (request.url === "/") {
    sendResponse("index.html", "text/html", response);
  } else {
    sendResponse(request.url, getContentType(request.url), response);
  }
}

function sendResponse(url, contentType, res) {
  let file = path.join(\_\_\_dirname, url);
  fs.readFile(file, (err, content) => {
    if (err) {
      res.writeHead(404);
      res.write(`File '${file}' Not Found!`);
      res.end();
      console.log("Response: 404 ${file}, err");
    } else {
      res.writeHead(200, { "Content-Type": contentType });
      res.write(content);
      res.end();
      console.log(`Response: 200 ${file}`);
    }
  });
}

function getContentType(url) {
  switch (path.extname(url)) {
    case ".html":
      return "text/html";
    case ".css":
      return "text/css";
    case ".js":
      return "text/javascript";
    case ".json":
      return "application/json";
    default:
      return "application/octate-stream";
  }
}

Reference

If you loved this post, Please share it on social media.