First Web Application – Node.js

Hope you are having a nice day. Today is Tuesday. Unless I am mistaken, it has been three weeks since Election Day and as far as I know the results for the presidential election are still up in the air. Hopefully we will find out the results in the next few days.

Next week is Thanksgiving Day. My wife and I were planning on having very few people over, but after further deliberations, we decided that we will skip and celebrate Thanksgiving 2021 with family and friends. Based on what is going on with COVID-19, it seems that a few vaccines will be made available early next year. By November 2021 we expect that most things would be back to normal; that is, the new normal.

Over the weekend my wife and I will be getting the main ingredients for the Thanksgiving 2020 celebration. We are planning on delivering some stuff to family members. No plans on getting together at all. Let’s keep it safe.

If you follow my blog, you are aware that I am currently reading “Get Programming with Node.js” by Jonathan Wexler. The book was published in 2019. Not sure if it was due to COVID-19, but several code snippets seem to be incorrect and incomplete. The good news is that you can visit a web site and download all the code and a PDF with the book contents. Have not read the PDF and might not do so. I prefer to read from a printed copy so I may easily underline and write notes. By the way, you will need a printed copy of the book if you wish to download the PDF and code.

It is better to read and experiment to make sure you get the most out of a book or video. I am doing so as I progress through the book.

By the way, as I was editing this post entry, I noticed that the blog has surpassed 4,000 subscribers. My sincere thanks to all that have subscribed!

In this capstone project, I made some changes to names and to the essence of the web site. If interested please get a copy of the book and get additional information about the project. I will show the steps I took. At this point the software is completed and does all the things required. That said; as you will see, there are some things that have left out for what appears to be a larger project. That is very typical of most projects. If this would be production code, you should remove all functions, variables, methods that are not used.

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

# **** ****
C:\Users\johnc>dir
11/10/2020  07:52 AM    <DIR>          .
11/10/2020  07:52 AM    <DIR>          ..
05/13/2020  02:07 PM                57 .angular-config.json
10/27/2020  02:16 PM    <DIR>          .atom
05/13/2020  02:03 PM    <DIR>          .config
10/27/2020  10:51 AM    <DIR>          .docker
09/22/2019  07:00 AM    <DIR>          .eclipse
03/15/2020  07:38 AM                59 .gitconfig
11/10/2020  07:59 AM    <DIR>          .lemminx
11/10/2020  07:46 AM    <DIR>          .m2
06/19/2020  07:40 AM               119 .node_repl_history
04/23/2020  10:11 AM    <DIR>          .p2
05/07/2020  07:11 AM         1,005,502 .pipwin
04/21/2020  07:07 AM    <DIR>          .pylint.d
09/22/2019  07:00 AM    <DIR>          .tooling
03/10/2020  02:53 PM    <DIR>          .vscode
11/01/2020  11:18 AM    <DIR>          3D Objects
11/01/2020  11:18 AM    <DIR>          Contacts
04/08/2020  06:59 AM    <DIR>          ContainsCloseNums
02/17/2020  02:52 PM    <DIR>          Documents
11/12/2020  03:27 PM    <DIR>          Downloads
09/22/2019  06:57 AM    <DIR>          eclipse
09/22/2019  06:59 AM    <DIR>          eclipse-workspace_0
11/01/2020  11:18 AM    <DIR>          Favorites
10/27/2020  10:48 AM    <DIR>          getting-started
11/01/2020  11:18 AM    <DIR>          Links
11/01/2020  11:18 AM    <DIR>          Music
05/13/2020  02:49 PM    <DIR>          my-app
09/12/2019  03:36 PM    <DIR>          NCH Software Suite
07/16/2020  06:39 AM    <DIR>          node_modules
11/01/2020  11:14 AM    <DIR>          OneDrive
07/16/2020  06:39 AM             3,871 package-lock.json
05/04/2020  02:42 PM    <DIR>          pipwin
10/28/2020  01:21 PM    <DIR>          Postman
10/27/2020  03:23 PM    <DIR>          PostmanAgent
11/01/2020  11:18 AM    <DIR>          Saved Games
11/01/2020  11:18 AM    <DIR>          Searches
05/11/2020  02:04 PM    <DIR>          source
11/01/2020  11:18 AM    <DIR>          Videos
05/05/2020  12:30 PM    <DIR>          vimfiles
06/10/2020  09:50 AM    <DIR>          workspace0
08/17/2020  09:26 AM    <DIR>          workspace1
10/10/2020  07:04 AM    <DIR>          workspace2
11/06/2020  04:12 PM    <DIR>          workspace3
11/16/2020  02:01 PM    <DIR>          workspace4
05/05/2020  12:51 PM             3,281 _viminfo

# **** ****
C:\Users\johnc>cd workspace4

# **** ****
C:\Users\johnc\workspace4>dir
11/16/2020  02:01 PM    <DIR>          .
11/16/2020  02:01 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/16/2020  07:40 AM    <DIR>          CombinationsJava
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/16/2020  02:03 PM    <DIR>          FirstWebApp      <===
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/13/2020  08:17 AM    <DIR>          second_server
11/13/2020  08:18 AM    <DIR>          simple_server

# **** folder for this project ****
C:\Users\johnc\workspace4>cd FirstWebApp

# **** initialize the project ****
C:\Users\johnc\workspace4\FirstWebApp>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: (firstwebapp) nonas_kitchen
version: (1.0.0)
description: Capstone project unit 1
entry point: (index.js) main.js
test command:
git repository:
keywords:
author: John Canessa
license: (ISC)
About to write to C:\Users\johnc\workspace4\FirstWebApp\package.json:

{
  "name": "nonas_kitchen",
  "version": "1.0.0",
  "description": "Capstone project unit 1",
  "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\FirstWebApp>dir
11/16/2020  02:07 PM    <DIR>          .
11/16/2020  02:07 PM    <DIR>          ..
11/16/2020  02:07 PM               243 package.json
11/16/2020  02:03 PM    <DIR>          public
11/16/2020  02:03 PM    <DIR>          views

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

# **** save http-status-code package as an application dependency ****
C:\Users\johnc\workspace4\FirstWebApp>npm i http-status-codes -S
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN nonas_kitchen@1.0.0 No repository field.

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

# **** ****
C:\Users\johnc\workspace4\FirstWebApp>npm i bootstrap -S
npm WARN bootstrap@4.5.3 requires a peer of jquery@1.9.1 - 3 but none is installed. You must install peer dependencies yourself.
npm WARN bootstrap@4.5.3 requires a peer of popper.js@^1.16.1 but none is installed. You must install peer dependencies yourself.
npm WARN nonas_kitchen@1.0.0 No repository field.

+ bootstrap@4.5.3
added 1 package from 2 contributors and audited 2 packages in 0.532s

1 package is looking for funding
  run `npm fund` for details

found 0 vulnerabilities

# **** ****
C:\Users\johnc\workspace4\FirstWebApp>type package.json
{
  "name": "nonas_kitchen",
  "version": "1.0.0",
  "description": "Capstone project unit 1",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "John Canessa",
  "license": "ISC",
  "dependencies": {
    "bootstrap": "^4.5.3",      <===
    "http-status-codes": "^2.1.4"
  }
}

The screen capture starts by getting to the folder I chose for the web application. You can name it as you please.

Once in the folder we need to initialize the project with npm init. The utility will prompt for some information. I names the project “nonas_kitchen” which is not the name used in the book. In addition, the entry point for the application was set changed to “main.js” instead of index.js.

When done, we display the contents of the folder. We have a couple new folders and a new package.json file. The contents of the file are then displayed.

We install the http-status-codes package because it allows us to use their definitions instead of defining the codes ourselves.

Based on the contents of the book, I was under the impression that we were going to use bootstrap in this project. Unless I missed something, we did not.

After the installation we check the package.json file. We now require bootstrap.

// **** exports ****
module.exports = {
    html: {
        "Content-Type": "text/html"
    },
    text: {
        "Content-Type": "text/plain"
    },
    js: {
        "Content-Type": "text/js"
    },
    jpg: {
        "Content-Type": "image/jpg"
    },
    png: {
        "Content-Type": "image/png"
    },
    css: {
        "Content-Type": "text/css"
    }
};

This file contains a list of content types supported by our application. Some of the values are not used at this time.

// **** ****
const port = 3000;

// **** required modules ****
http = require("http");
httpStatus = require("http-status-codes");
router = require("./router");
contentTypes = require("./contentTypes");
utils = require("./utils");

// **** register individual routes with the router module ****
router.get("/", (req, res) => {
    res.writeHead(httpStatus.OK, contentTypes.htm);
    utils.getFile("views/index.html", res);
});

router.get("/recipes.html", (req, res) => {
    res.writeHead(httpStatus.OK, contentTypes.html);
    utils.getFile("views/recipes.html", res);
});
  
router.get("/contact.html", (req, res) => {
    res.writeHead(httpStatus.OK, contentTypes.html);
    utils.getFile("views/contact.html", res);
});
  
router.get("/graph.png", (req, res) => {
    res.writeHead(httpStatus.OK, contentTypes.png);
    utils.getFile("public/images/graph.png", res);
});

router.get("/people.jpg", (req, res) => {
    res.writeHead(httpStatus.OK, contentTypes.jpg);
    utils.getFile("public/images/people.jpg", res);
});

router.get("/product.jpg", (req, res) => {
    res.writeHead(httpStatus.OK, contentTypes.jpg);
    utils.getFile("public/images/product.jpg", res);
});

router.get("/nonas_kitchen.css", (req, res) => {
    res.writeHead(httpStatus.OK, contentTypes.css);
    utils.getFile("public/css/nonas_kitchen.css", res);
});

router.get("/bootstrap.css", (req, res) => {
    res.writeHead(httpStatus.OK, contentTypes.css);
    utils.getFile("public/css/bootstrap.css", res);
});

router.get("/nonas_kitchen.js", (req, res) => {
    res.writeHead(httpStatus.OK, contentTypes.js);
    utils.getFile("public/js/nonas_kitchen.js", res);
});

router.post("/", (req, res) => {
    res.writeHead(httpStatus.OK, contentTypes.html);
    utils.getFile("views/thanks.html", res);
});


// ???? ????
console.log(`<<< router.get: ${router.get}`);
console.log(`<<< router.post: ${router.post}`);


// **** create server and listen on the specified port ****
http.createServer(router.handle).listen(port);

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

This is the main.js file. We specify to use port 3000. We then specify the required modules. Note that some modules come from existing packages, while others we will have to write in order to make them available to our application. Make sure you understand the folder structure for the project. The next step is to define the routes our application will support. All but one is associated with GET. There is only one route associated with POST. Not all the routes are used at this time. You can try a route that is not implemented to see what happens. We display some messages to make sure all is well during execution. We create the application which listens on port 3000. A message is display that our application is up and running.

{
"name": "nonas_kitchen",
"version": "1.0.0",
"description": "Capstone project unit 1",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "John Canessa",
"license": "ISC",
"dependencies": {
"bootstrap": "^4.5.3",
"http-status-codes": "^2.1.4"
}
}

This is the contents of the package.json file. I have nothing more to add to it at this point.

// **** required modules ****
const httpStatus = require("http-status-codes");
contentTypes = require("./contentTypes");
utils = require("./utils");


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


// **** routes ****
const routes = {
    "GET": {},
    "POST": {}
};

// **** functions to handle requests ****
exports.handle = (req, res) => {

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


    // **** ****
    try {
        routes[req.method][req.url](req,res);
    } catch (e) {
         res.writeHead(httpStatus.OK, contentTypes.html);
         utils.getFile("views/error.html", res);
    }
};

// **** ****
exports.get = (url, action) => {
    routes["GET"][url] = action;
};

// **** ****
exports.post = (url, action) => {
    routes["POST"][url] = action;
}

This is the routes.js file. It is used to dispatch requests based on the verb (GET or POST) and the URL.

The getJSONString() function is used to display information about requests.

The routes are used to track all GET and POST requests. These were populated by the main.js module.

When a request arrives, we display some information. This is only needed to debug and experiment. We then dispatch the request.

Note that the entries for GET and POST in our routes are exported so they can be accessed in the main.js module.

// **** required modules ****
const fs = require("fs");
httpStatus = require("http-status-codes");
contentTypes = require("./contentTypes");

// **** exports ****
module.exports = {

    // **** ****
    getFile: (file, res) => {
        fs.readFile(`./${file}`, (error, data) => {
        if (error) {
            res.writeHead(httpStatus.INTERNAL_SERVER_ERROR, contentTypes.html);
            res.end("Error encountered while serving content!");
        }
        res.end(data);
        });
    }
};

The utils.js file is used to respond to requests with files. Please try specifying files that do not exist and replace files with empty versions.

# **** ****
C:\Users\johnc\workspace4\FirstWebApp>dir /S
11/16/2020  04:47 PM    <DIR>          .
11/16/2020  04:47 PM    <DIR>          ..
11/16/2020  03:05 PM               384 contentTypes.js
11/17/2020  02:19 PM             2,160 main.js
11/16/2020  04:47 PM    <DIR>          node_modules
11/16/2020  04:47 PM               642 package-lock.json
11/17/2020  02:19 PM               329 package.json
11/16/2020  02:03 PM    <DIR>          public
11/17/2020  02:14 PM             1,027 router.js
11/17/2020  02:20 PM               531 utils.js
11/16/2020  05:09 PM    <DIR>          views

 Directory of C:\Users\johnc\workspace4\FirstWebApp\public

11/16/2020  02:03 PM    <DIR>          .
11/16/2020  02:03 PM    <DIR>          ..
11/16/2020  02:02 PM    <DIR>          css
11/16/2020  02:02 PM    <DIR>          images
11/16/2020  02:03 PM    <DIR>          js
               0 File(s)              0 bytes

 Directory of C:\Users\johnc\workspace4\FirstWebApp\views

11/16/2020  05:43 PM             1,237 contact.html
11/16/2020  05:31 PM               987 error.html
11/16/2020  05:20 PM             1,251 index.html
11/16/2020  05:31 PM             1,224 recipes.html
11/17/2020  02:16 PM               935 thanks.html

This illustrated the folder structure we used for this project. In the next post we will be using the Express.js web framework which will simplify and automate many manual operations we had to perform for this project. I will cover the next Capstone project in this blog.

[nodemon] restarting due to changes...
[nodemon] starting `node main.js`
<<< router.get: (url, action) => {
    routes["GET"][url] = action;
}
<<< router.post: (url, action) => {
    routes["POST"][url] = action;
}
<<< web server listening on port : 3000
<<<  req.method: "GET"
<<<     req.url: "/"
<<< req.headers: {
  "host": "localhost:3000",
  "connection": "keep-alive",
  "cache-control": "max-age=0",
  "dnt": "1",
  "upgrade-insecure-requests": "1",
  "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
  "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
  "sec-fetch-site": "none",
  "sec-fetch-mode": "navigate",
  "sec-fetch-user": "?1",
  "sec-fetch-dest": "document",
  "accept-encoding": "gzip, deflate, br",
  "accept-language": "en-US,en;q=0.9,es;q=0.8,it-IT;q=0.7,it;q=0.6"
}
<<<  req.method: "GET"
<<<     req.url: "/bootstrap.css"
<<< req.headers: {
  "host": "localhost:3000",
  "connection": "keep-alive",
  "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
  "dnt": "1",
  "accept": "text/css,*/*;q=0.1",
  "sec-fetch-site": "same-origin",
  "sec-fetch-mode": "no-cors",
  "sec-fetch-dest": "style",
  "referer": "http://localhost:3000/",
  "accept-encoding": "gzip, deflate, br",
  "accept-language": "en-US,en;q=0.9,es;q=0.8,it-IT;q=0.7,it;q=0.6"
}
<<<  req.method: "GET"
<<<     req.url: "/nonas_kitchen.css"
<<< req.headers: {
  "host": "localhost:3000",
  "connection": "keep-alive",
  "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
  "dnt": "1",
  "accept": "text/css,*/*;q=0.1",
  "sec-fetch-site": "same-origin",
  "sec-fetch-mode": "no-cors",
  "sec-fetch-dest": "style",
  "referer": "http://localhost:3000/",
  "accept-encoding": "gzip, deflate, br",
  "accept-language": "en-US,en;q=0.9,es;q=0.8,it-IT;q=0.7,it;q=0.6"
}
<<<  req.method: "GET"
<<<     req.url: "/people.jpg"
<<< req.headers: {
  "host": "localhost:3000",
  "connection": "keep-alive",
  "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
  "dnt": "1",
  "accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",
  "sec-fetch-site": "same-origin",
  "sec-fetch-mode": "no-cors",
  "sec-fetch-dest": "image",
  "referer": "http://localhost:3000/",
  "accept-encoding": "gzip, deflate, br",
  "accept-language": "en-US,en;q=0.9,es;q=0.8,it-IT;q=0.7,it;q=0.6"
}
<<<  req.method: "GET"
<<<     req.url: "/favicon.ico"
<<< req.headers: {
  "host": "localhost:3000",
  "connection": "keep-alive",
  "pragma": "no-cache",
  "cache-control": "no-cache",
  "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
  "dnt": "1",
  "accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",
  "sec-fetch-site": "same-origin",
  "sec-fetch-mode": "no-cors",
  "sec-fetch-dest": "image",
  "referer": "http://localhost:3000/",
  "accept-encoding": "gzip, deflate, br",
  "accept-language": "en-US,en;q=0.9,es;q=0.8,it-IT;q=0.7,it;q=0.6"
}

This is a screen capture of the console running our web application.

I just refreshed the web browser pointing to “localhost:3000” and all the activity that followed between the Chore web browser and the web application.

Hope you enjoyed solving this problem as much as I did. The entire code for this project can be found in my GitHub repository.

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 4,190 subscribers to this blog!!!

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

John

E-mail:  john.canessa@gmail.com

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.