Simple and Second Web Servers in Node.js

I am about to call it a day. It is dark and cold (25 F) in the Twin Cities of Minneapolis and St. Paul. I am in my office attempting to finish this post while my wife is upstairs in the living room wrapped in a blanket. Tomorrow morning we will wake up to a balmy 12 F. What else can I say?

I am reading and experimenting with the content of the book “Get Programming with Node.js” by Jonathan Wexler. I am currently on page 55. Expect to be done with towards the end of next month. At that time I will switch to experiment with Deno. Then I will move on to generate and post a first pass of a storage server. It should be a lot of fun.

In this post I will experiment with two basic and simple Node.js servers.

I am working on a Windows 10 machine. All the code is being generated using VSCode. So far all seems to be working fine. Will provide additional feedback in posts related to my journey on this book.

Microsoft Windows [Version 10.0.19041.630]
(c) 2020 Microsoft Corporation. All rights reserved.

C:\Users\johnc>cd workspace4

C:\Users\johnc\workspace4>dir
11/12/2020  02:59 PM    <DIR>          .
11/12/2020  02:59 PM    <DIR>          ..
11/03/2020  08:26 AM    <DIR>          AngleBetweenClockHands
10/15/2020  09:37 AM    <DIR>          BackspaceStringCompare
10/30/2020  07:59 AM    <DIR>          BSTToGST
10/21/2020  09:46 AM    <DIR>          CoinChange
10/21/2020  03:36 PM    <DIR>          CoinChange2
11/04/2020  03:39 PM    <DIR>          DatesInJava
11/12/2020  06:49 AM    <DIR>          DemoForRESTAPI
11/04/2020  08:37 AM    <DIR>          DoublesInJava
10/19/2020  10:23 AM    <DIR>          FibonacciDynamicComputing
11/10/2020  11:37 AM    <DIR>          javafunctional
11/07/2020  07:59 AM    <DIR>          JavaStreams
10/22/2020  06:20 AM    <DIR>          LRUCache
11/11/2020  11:12 AM    <DIR>          MaxLenConcatStrUniqueChars
10/26/2020  07:30 AM    <DIR>          PathWithMinimumEffort
11/12/2020  02:59 PM    <DIR>          simple_server    <=== previously created

C:\Users\johnc\workspace4>cd simple_server

C:\Users\johnc\workspace4\simple_server>dir
11/12/2020  02:59 PM    <DIR>          .
11/12/2020  02:59 PM    <DIR>          ..

C:\Users\johnc\workspace4\simple_server>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (simple_server)
version: (1.0.0)
description: book project
entry point: (index.js) main.js     <=== name for the server entry point
test command:
git repository:
keywords:
author: John Canessa
license: (ISC)
About to write to C:\Users\johnc\workspace4\simple_server\package.json:

{
  "name": "simple_server",
  "version": "1.0.0",
  "description": "book project",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "John Canessa",
  "license": "ISC"
}


Is this OK? (yes) y

C:\Users\johnc\workspace4\simple_server>dir
11/12/2020  03:02 PM    <DIR>          .
11/12/2020  03:02 PM    <DIR>          ..
11/12/2020  03:02 PM               232 package.json

C:\Users\johnc\workspace4\simple_server>type package.json
{
  "name": "simple_server",
  "version": "1.0.0",
  "description": "book project",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "John Canessa",
  "license": "ISC"
}

I opened a command prompt. I then changed directory to my current workspace folder. As you can see I had already created the simple_server folder using File Explorer. The folder was empty at the time. In the simple_server folder I initialized the new project. I then answered some questions. Note that I changed the proposed name of the server entry point from index.js to main.js as suggested in the book.

I then displayed the contents of the newly created package.json file.

C:\Users\johnc\workspace4\simple_server>npm i http-status-codes -S
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN simple_server@1.0.0 No repository field.

+ http-status-codes@2.1.4
added 1 package from 1 contributor and audited 1 package in 0.725s
found 0 vulnerabilities

C:\Users\johnc\workspace4\simple_server>dir
11/12/2020  03:07 PM    <DIR>          .
11/12/2020  03:07 PM    <DIR>          ..
11/12/2020  03:06 PM                 0 main.js
11/12/2020  03:07 PM    <DIR>          node_modules
11/12/2020  03:07 PM               393 package-lock.json
11/12/2020  03:07 PM               291 package.json

In order to be able to have our software return error codes using mnemonics, you should install the http-status-code module. Now we are ready to start writing the code for the Node.js server. I will use the VSCode IDE.

/**
 * file: C:\Users\johnc\workspace4\simple_server\main.js
 */

// **** constant(s) ****
const port = 3000;

// **** required modules ****
http = require("http");
httpStatus = require("http-status-codes");

// **** ****
app = http.createServer((req, res) => {

    // ???? ????
    console.log(`<<< received incomming request`);

    // **** ****
    res.writeHead(httpStatus.OK, {
        "Content-Type": "text/html"
    });

    // **** ****
    let responseMsg = "<h1>Hello, world !!!</h1>";
    res.write(responseMsg);
    res.end();

    // ???? ????
    console.log(`<<< response sent: ${responseMsg}`);
});

// **** server listening on this port ****
app.listen(port);

// ???? ????
console.log(`<<< server started listening on port: ${port}`);

We start by setting a constant to the port we will be using for our server. We then assign to two variables the http module which comes with Node.js and the http-status-codes which we installed later in the process. We then create an app which calls a anonymous function implemented as a lambda expression. We pass the request and response arguments which we will use to receive and respond to HTTP requests. In the lambda function we display a message to indicate we received a request. We set the type of response. We then create a response message which we format using HTML. We write the message to the response and end the connection. This last statement will send the response to the client. We then log the response we sent. Remember that the lambda expression will be called when the server receives a request. That code will not execute because we are not listening to requests yet. We start listening to requests in the specified port and display a message indicating that our server is ready to process requests on the particular port. All is well so far. We now need to start the Node.js server so it will parse and execute the contents of our main.js file.

C:\Users\johnc\workspace4\simple_server>nodemon main.js
[nodemon] 2.0.6
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node main.js`
<<< server started listening on port: 3000

<<< received incomming request
<<< response sent: <h1>Hello, world !!!</h1>
<<< received incomming request
<<< response sent: <h1>Hello, world !!!</h1>

You can start the server by using the node or the nodemon commands. I prefer to use nodemon because it will automatically restart when I save the code that I am editing in the VSCode IDE or in any other IDE you might be using.

As we can see the specified messages are display. I inserted a blank line to separate what happens when we issue a request using a client.

For a client I used the Chrome web browser. I entered “localhost:3000” and the server displayed with the following four lines. The first two were due to the GET request we sent. The second two are due to the request of an icon by the web browser. We will talk more about this later in this post.

So there you have it. We were able to create a very simple Node.js web server that handles the same way all requests. This is a good start, but the current software is not too useful.

C:\Users\johnc\workspace4>dir
11/12/2020  03:37 PM    <DIR>          .
11/12/2020  03:37 PM    <DIR>          ..
11/03/2020  08:26 AM    <DIR>          AngleBetweenClockHands
10/15/2020  09:37 AM    <DIR>          BackspaceStringCompare
10/30/2020  07:59 AM    <DIR>          BSTToGST
10/21/2020  09:46 AM    <DIR>          CoinChange
10/21/2020  03:36 PM    <DIR>          CoinChange2
11/04/2020  03:39 PM    <DIR>          DatesInJava
11/12/2020  06:49 AM    <DIR>          DemoForRESTAPI
11/04/2020  08:37 AM    <DIR>          DoublesInJava
10/19/2020  10:23 AM    <DIR>          FibonacciDynamicComputing
11/10/2020  11:37 AM    <DIR>          javafunctional
11/07/2020  07:59 AM    <DIR>          JavaStreams
10/22/2020  06:20 AM    <DIR>          LRUCache
11/11/2020  11:12 AM    <DIR>          MaxLenConcatStrUniqueChars
10/26/2020  07:30 AM    <DIR>          PathWithMinimumEffort
11/12/2020  03:36 PM    <DIR>          second_server    <==== or new server
11/12/2020  03:36 PM    <DIR>          simple_server

C:\Users\johnc\workspace4>cd second_server

C:\Users\johnc\workspace4\second_server>dir
11/12/2020  03:36 PM    <DIR>          .
11/12/2020  03:36 PM    <DIR>          ..

C:\Users\johnc\workspace4\second_server>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (second_server)
version: (1.0.0)
description: second_server
entry point: (index.js) main.js
test command:
git repository:
keywords:
author: John Canessa
license: (ISC)
About to write to C:\Users\johnc\workspace4\second_server\package.json:

{
  "name": "second_server",
  "version": "1.0.0",
  "description": "second_server",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "John Canessa",
  "license": "ISC"
}


Is this OK? (yes) y

C:\Users\johnc\workspace4\second_server>dir
11/12/2020  03:39 PM    <DIR>          .
11/12/2020  03:39 PM    <DIR>          ..
11/12/2020  03:39 PM               233 package.json

C:\Users\johnc\workspace4\second_server>type package.json
{
  "name": "second_server",
  "version": "1.0.0",
  "description": "second_server",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "John Canessa",
  "license": "ISC"
}

This screen capture illustrates that we repeat the steps we followed to create the previous Node.js web server, but this time we name it “second_server”.

If you are interested in experimenting on how to remove the second request from the web browser, the post named How to prevent favicon.ico requests? in Stack Overflow   should give you an idea. The suggestion is to add the following line to the main.js file:

res.writeHead(200, {'Content-Type': 'text/plain', 'Link': 'rel="shortcut icon" href="#"'} );

For now we will not add such modification. We first want to get better acquainted with the exchanges of data between the client and the web server.

C:\Users\johnc>curl --data "username=JohnCanessa&password=secret" http://localhost:3000
<h1>This message will show on the web browser.</h1>

By default, the web browser just sends GET requests. If you want to see some additional data sent via a POST command, you can use different applications (i.e., Postman) or utilities (i.e., cURL).

In this last example we sent some data using a POST request. This was to simulate a user on a client filling in a request to logon to our server. We used the cURL utility which is available in most (never generalize) platforms (i.e., Linux, macOS and Windows).

[nodemon] restarting due to changes...
[nodemon] starting `node main.js`
<<< server up and running on port: 3000
<<< method: "POST"
<<< url: "/"
<<< headers: {
  "host": "localhost:3000",
  "user-agent": "curl/7.55.1",
  "accept": "*/*",
  "content-length": "36",
  "content-type": "application/x-www-form-urlencoded"
}
<<< body ==>username=JohnCanessa&password=secret<==

After saving the current version of the main.js file, nodemon restarted the web server. I then issued the request via curl. The URL, method, headers and body contents were displayed. Of course our software is not able to detect or process such request. It just sends back the same HTML message.

/**
 * file: C:\Users\johnc\workspace4\second_server\main.js
 */

// **** port to listen on ****
const port = 3000;

// **** require these modules ****
http = require("http");
httpStatus = require("http-status-codes");

// **** create the server ****
app = http.createServer();

// **** process request ****
app.on("request", (req, res) => {

    // **** to hold chunck content (if any) ****
    var body = [];

    // **** collect data ****
    req.on("data", (bodyData) => {
        body.push(bodyData);
    });

    // **** end of data transmission ****
    req.on("end", () => {

        // **** convert array contents to string of text ****
        body = Buffer.concat(body).toString();

        // ???? ????
        console.log(`<<< body ==>${body}<==`);
    });

    // ???? log request contents ????
    console.log(`<<< method: ${getJSONString(req.method)}`);
    console.log(`<<< url: ${getJSONString(req.url)}`);
    console.log(`<<< headers: ${getJSONString(req.headers)}`);

    // **** response OK content type ****
    res.writeHead(httpStatus.OK, {
        "Content-Type": "text/html"
    });

    // **** response message ****
    let resMsg = "<h1>This message will show on the web browser.</h1>";

    // **** send response and end connection ****
    res.end(resMsg);
});

// **** server listening on this port ****
app.listen(port);

// ???? ????
console.log(`<<< server up and running on port: ${port}`);


/**
 * 
 */
const getJSONString = obj => {
    return JSON.stringify(obj, null, 2);
}

The code for the last version of our web server is quite similar, but we have added some functionality. When we process a request, we have a mechanism to collect the body of the data being sent by the client. The GET requests so far have not needed such feature, but the POST request did include additional data.

We create an array and as data arrives we collect it. The reason is that we do not know if and how large the data is going to be. So what we do is collect it and when the transmission from the client ends, we concatenate the contents of the data buffer into the body string. In our code we display the body contents.

The next step which is just used for debugging is to display in JSON format the method, URL and header contents. The getJSONString() function is located at the end of the main.js file.

You can go back to the screen capture when we issue a command to the server and see the information for the three objects we are displaying. As an exercise you could send different types of requests using curl and take a look at the data. You can also try different web browsers and look at the differences in the received values.

Back to the main.js file, we specify the type of response we will send. We generate an HTML response (the same for all requests) and then send the response and close the connection with the client.

The getJSONString() function returns a JSON (https://en.wikipedia.org/wiki/JSON) string with the contents of the object passed to it as an argument.

Hope you enjoyed solving this problem as much as I did. The entire code for this project can be found in the following two GitHub repositories: SimpleWebServer and SecondWebServer.

If you have comments or questions regarding this, or any other post in this blog, or if you would like for me to help out with any phase in the SDLC (Software Development Life Cycle) of a project associated with a product or service, please do not hesitate and leave me a note below. If you prefer, send me a private e-mail message. I will reply as soon as possible.

Keep on reading and experimenting. It is the best way to learn, become proficient, refresh your knowledge and enhance your developer toolset!

One last thing, many thanks to all 3,947 subscribers to this blog!!!

Keep safe during the COVID-19 pandemic and help restart the world economy.

John

E-mail:  john.canessa@gmail.com