DEV Community

Sachin Sarawgi
Sachin Sarawgi

Posted on

Basic HTTP Server Using NodeJS From Scratch


In this blog, we will see how to create an HTTP Server to handle GET, POST, PUT, DELETE request method type from scratch.

We need to have Node.js installed in our machine for the code to work. We will be using ‘http’ module provided out of the box to get the request and response object. We will not use any other custom library.

Steps For Creating A HTTP Server

  1. Create Server using http module and add a listener
  2. Do the necessary entry check for the request
  3. Extract request method type
  4. Writing handler for Http GET request
  5. Writing handler for Http POST request
  6. Writing handler for Http PUT request
  7. Writing handler for Http DELETE request

1. Create Server using http module and add a listener

First of all, we need to create a server that will listen to some particular port. So that if any request comes to that port the listener will be called.

We can do this using http module.

const server = http.createServer(requestListener);  
server.listen(8090);

createServer method accepts the listener as an argument. listen method takes the port number where it will keep listening.

Let’s see what empty requestListener method looks like.

const requestListener = function (req, res) {  
 //all the code goes inside it  
}

2. Do the necessary entry check for the request

Suppose we want our server to support REST API, and we want to have the following checks on request object:

  • Content-Type is application/json
  • Accept is application/json.

We will be using req object to get headers details and check the required values.

const REQUIRED_CONTENT_TYPE = 'application/json';  
const ACCEPT_ENCODING_1 = 'application/json';  
const ACCEPT_ENCODING_2 = '*/*';

const entryCheck = function (req) {  
  const contentType = req.headers["content-type"];  
  if (!contentType.includes(REQUIRED_CONTENT_TYPE)) {  
    throw new Error("Sorry we only support content type as json format.");  
  }  

  const accept = req.headers["accept"];  
  if (!(accept.includes(ACCEPT_ENCODING_1) ||  
accept.includes(ACCEPT_ENCODING_2))) {  
    throw new Error("Sorry we only support accept json format.");  
  }  
}

Let’s understand what’s going on.

  • First, we declare constant for content-type and accept header which our server will support
  • Next entryCheck is the method where we will check if the request headers have the required and matching details.
  • If either Content-type or Accept is not matching we will throw an error.

Now let’s see how we will call this method from type listener.

const requestListener = function (req, res) {  
  try {  
    entryCheck(req);  
  } catch (error) {  
    res.writeHead(400);  
    res.end(error.message);  
}
  • writeHead method takes the HTTP Status code, it could be any valid status code. It has some optional parameters too, the second is a status message and the third is headers.
  • end method takes the response body which will be shown to the user. After this method, the response is sent back and the whole request-response process is done.

Note: We can add multiple entry checks depending on our condition like a cookie, hostname/IP address, a particular header etc.


3. Extract request method type

We need to know HTTP method type to handle each one of them separately.

const requestListener = function (req, res) {  
  try {  
    entryCheck(req);  
    const methodType = req.method.toUpperCase();  
    ......

method property of request object gives us the Http method type like GET, POST, PUT, DELETE.

Next, we can use switch to handle different Http requests type differently

....  
switch(methodType){  
  case 'GET':  
    break;  
  case 'POST':  
    break;  
  case 'PUT':  
    break;  
  case 'DELETE':  
    break;  
}

4. Writing handler for Http GET request

Http GET requests are generally for finding an existing object by sending unique details

We can simply return a generic response in every kind of Http method type.

case 'GET':  
  res.writeHead(200);  
  res.end(`We received ${methodType} type request`);  
  break;

But instead of just returning simple response let’s create some object and apply operations on it.

Let us consider an employee object with the following fields:

{   
  "_id": "5ec02a534587193b1c607e2c",  
  "name": {  
    "first": "Pace",  
    "last": "Simmons"  
  },  
  "company": "MOLTONIC",  
  "email": "pace.simmons@moltonic.co.uk",  
  "phone": "+1 (941) 562-2930",  
  "address": "274 Dikeman Street, Somerset, Nevada, 6375"  
}

We will have an object containing an array of above employee objects.

let employeeData = [  
 {   
  "_id": "5ec02a534587193b1c607e2c",  
  "name": {  
    "first": "Pace",  
    "last": "Simmons"  
  },  
  "company": "MOLTONIC",  
  "email": "pace.simmons@moltonic.co.uk",  
  "phone": "+1 (941) 562-2930",  
  "address": "274 Dikeman Street, Somerset, Nevada, 6375"  
 },  
 ......  
]

Consider GET request where we will ask for particular employee detail by providing the _id value.

localhost:8090/5ec02a53d8ba79b6992ba757

Now we will need one method which will search for the request _id in the array of objects. We will write one method to search employee based on _id:

let findEmployee = (id) => {  
  return employeeData.find((employee) => {  
    if (employee._id === id)  
      return employee;  
  });  
}

Let’s rewrite the GET Http handler code

const requestListener = function (req, res) {  
  ....  
  case 'GET':  
    getMethodHandler(url, req, res);  
    break;  
  ....  
}

const getMethodHandler = (url, req, res) => {  
  const employeeId = url.substring(1);  
  const employee = findEmployee(employeeId);  
  if (!employee) {  
    res.writeHead(400);  
    res.end(`The employee with id ${employeeId} is not present.`);  
    return;  
  }  
  res.writeHead(200);  
  res.end(JSON.stringify(employee));  
}

We have written a separate method

  • First, we found the requested _id
  • We pass that _id to findEmployee method to get the employee object
  • Next, we check if an employee object is found or not if not we throw an error.
  • If everything goes fine we return the employee object in the response body.

5. Writing handler for Http POST request

Http POST requests are generally for inserting the new objects. In our case, we will add the received employee object to the array. Let’s write code for that method

let addEmployee = (employee) => {  
  employeeData.push(employee);  
}

Next, we need to handle the POST request and parse the request body to get the employee object we need to insert:

const requestListener = function (req, res) {  
  ....  
  case 'POST':  
    getRequestBodyAndGenerateResponse(req, res, postMethodHandler);  
    break;  
  ....  
}

const getRequestBodyAndGenerateResponse = (req, res, callback) => {  
  let body = '';  
  req.on('data', chunk => {  
    body += chunk.toString();  
  });  
  req.on('end', () => {  
    callback(res, JSON.parse(body));  
  });  
}

const postMethodHandler = (res, body) => {  
  try {  
    let reqBody = body;  
    addEmployee(reqBody)  
    res.writeHead(200);  
    res.end(`The Employee object with id is ${reqBody._id} added.`);  
}

Let’s understand what we did here.

  • We defined a method getRequestBodyAndGenerateResponse(req, res, postMethodHandler).
  • This method reads the data from req object by listening for ‘data’ event and append it to one variable body.
  • Once the ‘end’ event is triggered means the request body read completely, it parses the string into JSON and calls the callback function passed to it.
  • This callback function is the one that prepares the response object.
  • In the callback function first, we add the employee to employee array.
  • Then prepare a response and sent it to the user.

6. Writing handler for Http PUT request

Http PUT requests are generally for updating the old objects. In our case, we will update the received employee object if present inside the array. Let’s write code for that method

let findAndReplace = (employee) => {  
  let employeeFound = findEmployee(employee._id);  
  if (employeeFound) {  
    for (var key in employee) {  
      employeeFound[key] = employee[key];  
    }  
  return true;  
  } else {  
    return false;  
  }  
}

Next, we need to handle PUT request and parse the request body to get the employee object we need to update:

const requestListener = function (req, res) {  
  ....  
  case 'PUT':  
    getRequestBodyAndGenerateResponse(req, res, putMethodHandler);  
    break;  
  ....  
}

const putMethodHandler = (res, body) => {  
  let reqBody = body;  
  findAndReplace(reqBody);  
  res.writeHead(200);  
  res.end(`The Employee object with id is ${reqBody._id} replaced.`);  
}

Let’s understand what we did here.

  • We defined a method getRequestBodyAndGenerateResponse(req, res, putMethodHandler).
  • This method reads the data from req object by listening for ‘data’ event and append it to one variable body.
  • Once the ‘end’ event is triggered means the request body read completely, it parses the string into JSON and calls the callback function passed to it.
  • This callback function is the one that prepares the response object.
  • In the callback function first, we update the received employee object inside the employee array.
  • Then prepare a response and sent it to the user.

7. Writing handler for Http DELETE request

Http DELETE requests are generally for deleting an existing object. In our case, we will delete the received employee object _id from the array. Let’s write code for that method

let deleteEmployee = (id) => {  
  let length = employeeData.length;  
  while (length--) {  
    if (employeeData[length]  
    && employeeData[length]["_id"] === id) {  
      employeeData.splice(length, 1);  
      return true;  
    }  
  }  
  return false;  
}

Next, we need to handle the DELETE request, get _id of the employee, and delete that object from the array.

const requestListener = function (req, res) {  
  ....  
  case 'PUT':  
    deleteMethodHandler(url, req, res);  
    break;  
  ....  
}

const deleteMethodHandler = (url, req, res) => {  
  const employeeId = url.substring(1);  
  const response = deleteEmployee(employeeId);  
  res.writeHead(200);  
  res.end(`The employee with id ${employeeId} is deleted.`);  
}

Let’s understand what we did here.

  • First, we found the requested _id
  • We pass that _id to deleteEmployee method to delete the employee object
  • If everything goes fine we delete the employee object.
  • Then prepare a response and sent it to the user.

You can find the above code here. I tried to convert it into a modular format by separating data, methods, and using a module export-import feature of JS.

If you enjoyed reading this, don’t forget the like. 👏

Thank you.

Top comments (2)

Collapse
 
patarapolw profile image
Pacharapol Withayasakpunt

A real world usage for this -- Nuxt serverMiddleware. Honestly, it's a little painful to write, but you actually made it less painful...

Collapse
 
sachinsarawgi profile image
Sachin Sarawgi

Thanks Pacharapol.... I will take this on a positive note.