JavaScript Objects

Notes from Chapter 6 Objects, in JavaScript, the Definitive Guide by Flanagan.

“Objects are JavaScript’s most fundamental datatype.”

An object is a composite value.   It is an unordered collection of properties, each of which has a name and a value.

Property names are usually strings, so we can say objects map strings to values. Property names can also be JS identifiers.

An object cannot have more that one property of a given name.  The value may be any JS value, or it may be a getter or setter function (or both).

Any value that is not a string, a number, a Symbol, true, false, null or undefined is an object.

JS objects inherit other properties, like methods, from its “prototype”.

JS use the term own property to refer to non-inherited properties.

Each property has three property attributes.

    • The writable attribute specifies whether the value of the property can be set.
    • The enumerable attribute specifies whether the property name is returned by a for/in loop.
    • The configurable attribute specifies whether the property can be deleted and whether its attributes can be altered.

By default all objects we create are writable, enumerable, and configurable.

6.2 Creating Objects

Objects can be created with object literals, with the new keyword, and with the Object.create() function.

Almost every JS object inherits from a prototype object.  Objects created from object literals and from new Object() constructor inherit from Object.prototype.

Object Literals

An object literal is a comma-separated list of colon-separated name:value pairs, enclosed within curly braces.

let empty = {};
let point = {x:0, y:0};
let p2 = {x:point.x, y:point.y+1};
let book = {
    "main title": "JavaScript",           // identifiers cannot use spaces
    "sub-title":"The Definitive Guide",   // identifiers cannot use -
    for: "all audiences"                  // for is reserved, no quotes
    author: {
        firstname: "David",
        surname: "Flanagan"
    }
};

Creating Objects with new

The new operator creates and initializes a new object.  The new keyword must be followed by a function invocation.  A function used in this way is called a constructor.

let o = new Object();
let a = new Array();
let d = new Date();
let r = new Map();

Object.create()

Object.create() creates a new object, using its first argument as the prototype of the newly created object.

let o1 = Object.create({x:1, y:2});
let o2 = Object.create(Object.prototype);  // o2 is like {}

If you want to pass an object to a library function an ensure that the library function does not modify the object, you can create a temporary object using the existing object as the prototype for Object.create(), and pass the temporary object to the library function.

let o = {x: "don't change this value"};
library.function(Object.create(o));

6.3 Querying and Setting Properties

We can get the value of a property using the dot operator or [].

let author = author.firstname;
let title = book["main title"];

We set the value of property using the dot operator or [].

book.edition = 7;
book["main title"] = "ECMAScript";

Objects as Associative Arrays

JS objects are associative arrays, hence why we can use [] to get and set properties.

A JS program can dynamically create any number of properties in an object.

When you use the dot operator, the property name is an identifier and is static.  When you use [], the string within the [] can be created dynamically.

let addr = ""
for(let i= 0; i < 4; i++) {
    addr += customer[`address${i}`] + "\n";
}

function addstock(portfolio, stockname, shares) {
    portfolio[stockname] = shares;
}

Inheritance

Objects have a set of “own properties” and inherit properties from their object prototype.

If you search for a property, the “own properties” are queried first, if the property is not found then the object’s prototype is queried, if the property is still not found and the object prototype has an object prototype, then it is queried, and so on, until it reaches an object prototype with a null prototype.

If you query for a property that does not exist, undefined is returned.

When you want to set a property’s value, the property is queried.

An attempt to set a property p of an object o will fail under the following conditions.

    • o has an own property that is read-only
    • o has an inherited property that is read only
    • o does not have an own property; o does have an inherited property with a setter method; o’s extensible attribute is false.

If the set operation creates a new property, the new property is always an own property.

    • If the property does not exist as an own property or an inherited property, a new own property is created.
    • If the property is an own property then its value is changed.
    • If the property is not an own property and but is an inherited property with a setter method, the setter method is called on the original object and if the setter creates a new property, the new property is an own property.
    • If the property is not an own property but is an inherited property (without a setter method), a new own property is created overriding the inherited property.

Property Access Errors

You cannot access a property of a null or undefined object. If you are unsure if an object or property is null or undefined, do one of the following.

let name = undefined;
if (book) {
    if (book.author) {
        name = book.author.surname;
    }
}

name = book && book.author && book.author.surname;

name = book?.author?.surname;

Deleting Properties

 

 

The Development Process

In this module, we’ll use Visual Studio Code (VSC) to create a simple app, test it with a browser, create a GitHub repository for our code, and push our code to Heroko so that it accessible to the public.  We’ll use this same process each time we create a new app in this class.

Create a VSC Project

Using the file explorer application that came preloaded on your laptop, create a directory that will hold all of the project files for your app.  You should give the directory a name that is similar to your app name.  For example, in this tutorial we’ll create a simple Hello World app, so a reasonable name for the directory would be something like hello-world.  The location of the directory doesn’t matter, you just have to know where it is.

Next, start VSC.  When you start VCS for the first time you may see a Get Started page.  Click the x in the tab to close it.

Now, on the left side of the app you should see a set of icons.  If you hover over the top one it should say Explorer.  Click on the Explorer icon.  This should display a drop down menu titled NO FOLDERS OPENED.  If you click on the drop down arrow, you should see a button labeled Open Folder.  Click on the Open Folder button and choose the directory that you created for the project.  You should now see the name of your project directory at the top of the Explorer pane.

Set Up Project Directory Structure

Next, we want to create a directory inside our project directory to hold the source code for our app.  There are many ways to create subdirectories in VSC.  One way, in the Explorer pane, is to click on the directory in which you want to create a subdirectory.  This should reveal an icon to create a new Folder (i.e. directory).  If you click on the new Folder icon a text box is revealed in which you can enter the name of the new directory.

Add a subdirectory of your root project folder and name it src.

In future projects, we’ll add many other subdirectories to keep our project organized.

Initialize npm

VSC has a built in terminal application that we can use to issue commands.  You can open the VSC terminal by selecting View -> Terminal from the app’s menubar.  Doing so will open the terminal in a separate pane inside VSC.

If you run the pwd (print working directory) command in the VSC terminal, you should see that your working directory is project’s directory.

$ pwd

Next, we need to set up an npm package for our node application.

To do so, run the following command in the VSC terminal.  When you do, you will be prompted a series of questions.  You can press enter for each to choose the default values.  Before entering yes when asked Is this OK? notice that the value for the main property is index.js.  This is the name of the file that will be executed first when your app runs.

$ npm init

You can view documentation for all of the npm commands (including init) at https://docs.npmjs.com/cli/v8/commands/.

In the future, to avoid having to press enter for each of the default values, you can simply run the following command to bypass all of the prompts.

$ npm init -y

When you ran npm init, it created a file named package.json.  Open the file and view it’s contents.  This file contains all of the information needed by the node runtime to run the app.

Install npm Packages

Recall that npm packages are libraries of code (often developed by other developers) that we can install in our project and then import and and use in our apps. You can find information about all npm packages at https://www.npmjs.com.

The npm install (or npm i) command downloads and installs into our project a package and any packages that it depends on.

In our Hello World app, we are going to use two packages.  Run the following commands from the VSC terminal to install the nodemon and express packages into your project.

$ npm i nodemon --save-dev
$ npm i express

After you’ve run the commands you should notice that a new subdirectory named node_modules was created in your project. If you expand this subdirectory you will see that the nodemon and express packages were installed in it.  What are all of the other packages?  They are the packages that are dependencies of nodemon and express.  They were installed automatically when nodemon and express were installed.  Open up a few and explore their code.

Per the npm website, “nodemon is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected.”

The –save-dev flag specifies that the package should only be installed when the project is under development or testing, but not when installed on a live environment, like Heroku.

Express is a framework that provides a set of features that make developing web applications quick and easy.  You can view the documentation including guides and their API reference at http://expressjs.com.

If you reopen up package.json you should see two new property; one named devDependencies and another named dependencies.  These properties contain the packages that our app is now dependent on.  So when we push our code to Heroku and Heroku installs our application on their servers, the express package will also be installed, but the nodemon package will not since it is a development dependency.

Add Scripts to package.json File

Also notice the scripts property in package.json.  We want to create two new scripts.  One named start that will run the file named index.js (which we haven’t written yet) using node, and one named dev that will run the file named index.js using nodemon.

Modify the scripts property so that it reads as follows:

...
"scripts": {
  "start": "node src/index.js",
  "dev": "nodemon src/index.js"
},
...

You can save the file using CTRL + s on Windows and CMD + s on Macs.

Write the App

Now let’s write some code.  In the src subdirectory, create a file named index.js and add the following code to it.  Don’t forget to save the file after editing.

const express = require('express')
const app = express()
const port = process.env.PORT || 3000

app.get('', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log('Server is up on port ' + port)
})

Test the Node.js App

Now let’s start up our app using node by issuing the following command. You should see Server is up on port 3000 printed to the terminal.

$ node ./src/index.js

Now open a browser and navigate the browser to http://localhost:3000.  You should see Hello World! in the browser.

Localhost is a reserved hostname refers to the machine on which the client (e.g. the browser) is running from.  Your browser connected to port 3000 on your laptop.  Since your app is listening to port 3000 on your machine, when the browser connected to port 3000, your app responded by sending the browser the text Hello World! 

Stop the app by pressing CTRL + c in the terminal.

Now restart the app by running the npm script named dev by running the following command in the terminal.

$ npm run dev

Verify that the app is running using your browser.

Now change index.js so that the app send the client some other text other than Hello World and save the file.

Notice that nodemon automatically restarted node.  Use the browser to verify that the app is now sending the new text.

When you’re finished experimenting, shut down the app using CTRL + c.

Create a Git Repository

Let’s create a git repository for our app. In the terminal, change the working directory to the project’s root directory (if not already at the root).  Recall we can view the working directory using the pwd command, and change the working directory using the cd command.

Now initialize a git repository by issuing the following command in the terminal.

$ git init

We don’t want git to track the files in the node_modules directory since it doesn’t contain our code.  To prevent git from tracking these files we need to first create a new file named .gitignore in the project’s root directory.

Inside .gitignore we add the following line so that the node modules are not copied to the repo.

node_modules/

We can verify that this directory is not tracked by running the following command and noticing that node_modules/ is not listed.

$ git status

We can now add all of the tracked files and directories to the git staging area using the following command.

$ git add .

Next we want to commit the changes to the staged files using the command below.

$ git commit -m "initial commit"

Push Code to GitHub

Now on GitHub, create a new repository by clicking the + in the upper right corner of the webpage. Name the repository and leave all the defaults.

After you’ve created a repository, you should see a box on the webpage that reads Quick setup — if you’ve done this kind of thing before. In that box press, press the SSH button. This changes the commands need to push code using SSH key authentication.

Next, scroll down on the webpage to see some commands listed under …or push an existing repository from the command line. These are the commands we’ll be issuing to push our code from our laptops to GitHub.

Open the terminal in VSC and change the working directory to your app’s root directory. Then issue the following commands to push your code to GitHub, replacing XXXX with your GitHub username and replacing YYYY with the GitHub repository name that you used when you created the repository on GitHub.

$ git remote add origin git@github.com:XXXX/YYYY.git
$ git branch -M main
$ git push -u origin main

If you refresh your GitHub page, you should see your app’s code.

Deploy the App to Heroku

Next, we need to initialize an app on Heroku. This can be done using the heroku create command followed by a name of our application. The name of the application will be used as part of the URL to access the application on heroku.com so it has to be a unique app name that Heroku has not already created.

In VSC, change the working directory to the root directory of your app.  Then issue the following command, replacing unique-app-name with a name that you are sure Heroku has not seen before.  For example, you might use your GitHub user name followed by the name of your VSC project.

$ heroku create unique-app-name

If successful, you will see two links. The first is a link to the live URL that our app will run at. If you navigate to the link in a browser you’ll see a banner stating “Heroku | Welcome to your new app!”. Bookmark the link.  The second is a git repository hosted on heroku.com.

When we initialized the Heroku app above using the Heroku CLI, it created a local git remote named heroku pointing to the git repository on the Heroku server. To see the remote, we can run the following command.

$ git remote

Now to push our code to the Heroku server we run the following command.

$ git push heroku main

After the code is pushed to Heroku we see from the terminal messages that Heroku installed our app and started the application. If we navigate to the link provided in the terminal, we can witness our app is truly running on the Heroku server.

My app is running at https://n0code-hello-world.herokuapp.com.

Setting Up a Development Environment

In this course we’ll use our personal laptops to develop Node.js applications, test our applications using a browser and Postman, and deploy our application to a live site on Heroku.com.  Along the way, as we add functionality to our applications, we’ll learn how to use advanced JavaScript features like Promises, incorporate a MongoDB database, use publicly available node modules created by the Node.js community, and make calls to various online services through their APIs.

To begin, in this module, we’ll install and configure, on our laptops, the necessary software needed to develop, test, and deploy Node.js applications.

If at anytime you run into a problem, please STOP and notify your instructor so that he/she can help you.

In the instructions below, I will often ask you to type a command at a command prompt by showing you the command in box, like the one below.

$ git --version

In the command shown above, I denote the command prompt using the $ symbol.  Your command prompt may be some other symbol.  Please note that the command that you are asked to type does not include the command prompt itself.  For example, if I ask you to type the command shown in the box above, you would type git --version, not $ git --version.

As you follow the instructions below, explore the websites and try to understand how each application will be used in the development process.

Let’s get started.

Install Git

Git is a tool developed by the inventor of Linux, Linus Torvalds, that allows us to archive our code locally on our laptops in repositories and copy the repositories from our laptops to other online services like GitHub.com and Heroku.com.

Windows Instructions

If you have a Windows laptop you can get Git by navigating a browser to GitForWindows.org and downloading the appropriate version of Git.  When you install Git for Windows, pay attention to the prompts, and make sure you install the GitBash terminal applications during the Git installation process.  We’ll be using GitBash at various times throughout the course.

Like now.  Once Git is installed, launch your terminal application (GitBash) and type in the following command.  You should see a version number printed to the screen.

$ git --version

MacOS Instructions

If you have a Macbook, launch Apple’s App Store app and install Apple’s XCode software development platform (it’s free).

Now, using your Spotlight Search application, find and run your Terminal application.  In the Terminal application, type the following command.

$ git --version

If you see a version number, you have Git installed on your macbook.  If not, MacOS will ask you if you want to install Git.  Follows the prompts to install Git.

Set Up Git

We need to configure Git so that it knows our identity and we need to change the default branch name that Git uses from ‘master’ to ‘main’.

If you’ve used Git on your laptops prior to this course you may have already configured Git to know your identity so you don’t have to do it again, but you likely have not changed the default branch name.

Please use the instructions in the git-scm.com tutorial named 1.6 Getting Started – First-Time Git Setup to set up your identity (if you haven’t already) and change the default branch name from master to main.  Note that you only have to perform the instructions in the sections titled Your Identity, Your default branch name, and Checking Your Settings.  You should use your full name when setting user.name, but can use any valid email address when setting user.email.

If you haven’t already, run the following command to verify your Git config settings.

$ git config --list

Install Python

Some of the Node.js modules that we’ll download and use in our applications are written in Python, therefore we need to install Python on our laptops.

Please navigate your browser to https://www.python.org/downloads/, download the appropriate installer for the latest version of Python, and run the installer.

After you’ve installed Python, run the following command in your terminal application.  You should see the version number.

$ python3 --version

If you get an error stating that python3 does not exist, try the following command.

$ python --version

Install Node.js

If you’ve developed Java programs, you’re familiar with using a Java Runtime Environment (JRE), for example using java on the command line, to run the Java programs that you’ve written.   Node.js is analogous to a JRE.  Node.JS is a runtime application that runs JavaScript programs.  In this course we will develop internet applications in JavaScript and use Node.js to run them.

Navigate a browser to the Node.js website at https://nodejs.org/en/, download the installer for the Current version, and run the installer.

After you’ve installed Node.js, run the following command in your terminal app.  You should see the version number of node that you’ve installed.

$ node -v

The Node.js installer, also installs a program named npm which stands for Node Package Manager.  We will use npm to install packages (i.e. libraries) into our application work space for our applications to use.  Run the following command in your terminal to verify that npm is installed.  You should see the version of npm that is installed on your laptop.

$ npm -v

Update npm

To ensure we have the latest version of npm, run the following command in your terminal.  Note that the -g flag informs npm to install the latest npm package globally, that is, one copy of npm will installed on your laptop that can be used from any working directory.  Later we will omit the -g flag when we want to install a package for a single application that we are developing.
$ npm install npm@latest -g

Install Visual Studio Code

In this course we are going to use the Visual Studio Code editor.  Please use a browser to navigate to https://code.visualstudio.com, download the appropriate installer, and run the installer.
Please note that throughout the rest of these course notes I will refer to the Visual Studio Code editor simply as VSC, rather than having to write out Visual Studio Code each time.

Next, navigate your browser to https://marketplace.visualstudio.com/items?itemName=waderyan.nodejs-extension-pack and install the Node.js Extension Pack for Visual Studio Code.

Create a SSH Key Pair

SSH is a communication protocol that can be used to securely copy data from one server to another.  In order to authenticate ourselves to a server we can either provide the server with a user name and password, or we can use a pair of SSH key (one private and one public).

We’re going to use the git program to copy our application code from our laptops to the Heroku and GitHub servers and authenticate ourselves using SSH keys.

Type the following command in your terminal to create a pair of SSH keys, replacing joe@example.com with one of your email addresses.

$ ssh-keygen -t rsa -b 4096 -C "joe@example.com"

The ssh-keygen program will ask you to enter the name of a file in which to save the key.  Write down the default location (you will need this later), then press enter, to use the default location. The ssh-keygen program will then ask you to enter a passphrase.  Press enter, for no passphrase, and press enter a second time to accept the empty passphrase.

You can verify that your SSH key pair files were created by using the cd command in the terminal to change your working directory to the location that the key files were saved.  For example, if when you ran ssh-keygen, it stated that the id_rsa file was created in /Users/joe/.ssh, you would change the working directory to /Users/joe/.ssh using the following command.

$ cd /Users/joe/.ssh

Then to view the contents of the directory you can use program named ls.

$ ls

You should see at least two files; one named id_rsa and one named id_rsa.pub.  The file named id_rsa is your private key file.  Keep it secret.  Anyone with access to it can masquerade as you. The file named id_rsa.pub is your public key which we will later provide to Heroku and GitHub.

You can view the contents of the files using the cat program.

$ cat id_rsa
$ cat id_rsa.pub

Next, start the ssh agent, if it is not already running, by running the following command.

On MacOS, run

$ eval "$(ssh-agent -s)"

On Windows, run

$ eval $(ssh-agent -s)

You should see an agent pid (process identifier) printed to the screen.

Alas, provide the ssh agent with your private key by running the following command.

On MacOS, run

$ ssh-add -K ~/.ssh/id_rsa

On Windows, run

$ ssh-add ~/.ssh/id_rsa

You now have a pair of ssh keys, started the ssh agent, and registered your private key with the ssh agent.

Create a Heroku.com Account

Navigate a browser to Heroku.com and create a free account.  After submitting the application, click the link in the mail that will be sent to you, and complete the process by choosing a password.

Install and Set Up the Heroku CLI

Heroku.com is a website that provides hosting for Node.js (and other) web applications.  After we’ve developed and tested our web applications on our laptops we’ll push our application code to Heroku which will run our apps on their publicly available servers and provide us with a URL which we can share with others so they can use the applications we’ve created.

The Heroku Command Line Interface (CLI) is an application that we’ll use to push our code to the Heroku servers.

Windows Instructions

If you have a Windows laptop, navigate a browser to the Heroku CLI webpage at https://devcenter.heroku.com/articles/heroku-cli, download the appropriate installer, and install the CLI.

After you’ve installed the CLI, verify that it is installed by running the following command from your terminal.  You should see the version of heroku installed on your laptop.

$ heroku -v

MacOS Instructions

If you have a macbook, in order to install the Heroku CLI, you first need to install Homebrew.  Homebrew is a package manager that can install open source software on your macs.

To install Homebrew, navigate your browser to the Homebrew website at https://brew.sh.  Next, copy the command listed below the Install Homebrew heading on the webpage, paste the command in your terminal application, and run the command.

After you’ve installed Homebrew, navigate your browser to the Heroku CLI webpage at https://devcenter.heroku.com/articles/heroku-cli.    Under the macOS heading, you’ll see a Homebrew command that you can run from your terminal to install the Heroku CLI.  Copy the command, paste the command in your terminal application, and press Enter.

After you’ve installed the CLI, verify that it is installed by running the following command from your terminal.  You should see the version of heroku installed on your laptop.

$ heroku -v

Next, log in to heroku by typing the following command in the terminal.

$ heroku login

At the prompt, press any key and a browser screen will open with a Log In  button. Press the button to log in. Once logged in you can close the browser tab. In the terminal, you should see a message stating your Heroku username.

Finally, we need to provide Heroku with our public ssh key.  To do so, in your terminal, issue the following command.

$ heroku keys:add

If you have multiple public ssh keys you will see a list to choose from. If you only have one, you will see the public key’s path displayed and a prompt asking if you want to upload it to Heroku. Enter Y and press enter. If you see a message saying “Uploading … done”, then it was successful.

Create a GitHub Account

Next, if you don’t have a GitHub account already, navigate to GitHub.com, and sign up for a new account.  GitHub will send you a confirmation email.  Please click on the link in the email to complete the process.

Set Up GitHub

Log onto GitHub.com.

Click on the icon in the upper right corner, scroll down, and choose Settings. Othe left hand pane of the Settings screen, choose SSH and GPG keys. Press the New SSH key button. Enter a name for the key (e.g. My Laptop), and copy the contents of your id_rsa.pub file (your public key) into the key field.

Verify that you can communicate with GitHub using ssh by running the following command from the terminal.

$ ssh -T git@github.com

If successful, you should see a message that reads “You’ve successfully authenticated, but GitHub does not provide shell access.”

Install Postman

Web applications have two main components, a browser (client) facing component and a backend (server) component.  Since this course is focusing on the server side of web development, we will often not fully build out the client side.  As such, we won’t always be able to use a browser to test our web applications.  Postman is a program that we can use to test the APIs provided by our web applications.

You can install Postman by navigating a browser to postman.com/downloads, download the appropriate app, and installing it. Note that you do not need to create an account in order to use Postman for our purposes.  When we start Postman it will ask us to sign in, but it also provides a link to skip the log in process.

// End of Setting Up a Development Environment

Node Guide

Terminal Commands

node -v
npm install -g n
n latest

npm init
npm init -y
npm i package[@version]
npm i package[@version] -g

node app.js
nodemon app.js
nodemon src/app.js -e js/hbs

git status
git add .
git commit -m "message"


Common npm Modules

    • fs
    • validator
    • yargs
    • postman-request
    • express
    • path
    • hbs
    • mongodb
    • mongoose
    • bcrypt

 

Node Objects & Properties

    • process.argv[]
    • JSON.stringify()
    • JSON.parse()
    • document.querySelector()

 

 

 

 

Installing and Exploring Node.js

Part 4. Installing Node.js and Visual Studio Code

Visit nodejs.org > Downloads. Download the Current version and install.

Test node.js by opening WebStorm and typing the following in the WebStorm terminal to see the node version that is installed.

$ node -v
Updating node.js

We can update Node.js to the latest version using Node.js’ version manager named n. We can use npm to install n, as shown below.

$ npm install -g n

Once n is installed, we can use n to update Node.js to the latest version.

$ n latest

5. What is Node.js?

Per the Node.js website, “Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine.”

Chrome uses the V8 engine (written in C++) to run JavaScript code that has been dowloaded from a web server. Node.js is able to extend the capabilities of JavaScript by adding to V8 C++ binding to system calls. This allows Node.js’ V8 to do things that would be unsafe in a browser, such as access the computer’s file system.

You can run JavaScript code from the browser’s console and from the node REPL (read-eval-print loop). Try.

  • The browser has access to the window object and document object.
  • Node does not have access to the window object or the document object.
  • Node has access to an object named global and an object named process.
  • The browser does not have access to the global and process objects.

Verify this by trying to access each of these objects in both the browser’s console and in the node REPL.

$ window
$ document
$ global
$ process.exit()

Part 6. Why Should I Use Node.js?

Per the node.js website, “Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.” Event-driven refers to the ability to register callbacks that will be executed when an I/O operation has completed. Non-blocking refers to the fact that Node.js does not have to wait for an I/O operation to complete before executing the next line of JavaScript.

npm is a package management service that was installed on your machine when Node.js was installed. We can use it to install pre-written packages on our machine and use them in our apps. You can browse all of the libraries and packages that npm has available by visiting npmjs.com.

Part 7. Your First Node.js Script

In WebStorm create a directory on your web server for your node code. Create a file within that directory named hello.js. Add to hello.js the following code.

console.log('hello')

Now run hello.js from WebStorm’s console using node.js. From the console, change the working directory to the directory containing hello.js. Next, execute hello.js using node by typing the following command.

$ node hello.js

You should see the word hello printed to the console.

Node.js’ website contains documentation for the Node.js API. Take some time to browse the documentation and see what is available.

Node.js Module System (Notes App)

9. Importing Node.js Core Modules

Some of the functionality in the Node.js API is provided by built-in global objects (like console). Other functionality is provided by modules that must be loaded using the require() function. For example, to load the file system module, as specified in the documentation, we use the following statement.

const fs = require('fs')

Here, require() loads the fs module and returns a reference to an object which we store in the variable fs. This object gives us access to the file system functionality available in Node.js as specified by the API documentation.

With it we can do things such as write to a file. For example the code below creates a file named notes.txt and writes to that file the string “created by node.js”.

fs.writeFileSync('notes.txt', 'created by node.js')

Test this by adding these lines of code to a file named write_file.js. Then run it with Node.js.

Challenge

Append the current Date to notes.txt using fs.appendFileSync().

const fs = require('fs')
fs.appendFileSync('notes.txt', '\n' + new Date().toString())

Part 10. Importing Your Own Files (a.k.a. modules)

The benefits to separating an app’s code into multiple JavaScript files include:

  • debugging: it is easy to locate and fix bugs when they are in smaller files.
  • testing: once the code in a file has been tested we can rely on its correctness.
  • reusability: the functionality contained in one file and be reused without causing code bloat.

We can consider the code in a file as a module. As we’ve seen in Part 9, JavaScript provides us with a mechanism to run the code in one file from another file using the require() method.

Passing Data From One Module to Another

A variable’s scope is restricted to the module in which it is declared. In order to exports data from a module and into another, as the fs module does, we set the module.exports field equal to the data we wish to export. For example, suppose we have a variable named name in a module named utils.js that we want accessible in other JavaScript files. Then in utils.js, we use the following code.

const name = 'Ian'
module.exports = name

To use the value(s) exported in another JavaScript file, say import_module.js, we use require() as shown below.

const firstName = require('./utils.js')
console.log(firstName)                   //prints Ian

Note the relative path to utils.js used by require(). Also note that the value exported by utils.js can be stored (in import_module.js) in a variable with any arbitrary name.

We can also export functions. In the example, below we modify utils.js to export a reference to an anonymous function expressed using a lambda expression.

const sum = (a,b) => a + b
module.exports = sum

We can import sum() using require() and then call the method as shown below. Note that utils.js exports a function named sum, yet we when we import the function with require we change the name to add.

constant add = require('./utils.js')
console.log(add(4,-2))                 //prints 2

Part 11. Importing npm Modules

When we installed Node.js the program npm was installed as well. The npm program can be used to install into a project’s code base modules from the npm website. Once a module is installed in a project’s code base we can load the module using require().

Before we can use npm in a particular project, we must initialize npm in the project (shown below). This creates a single JSON configuration file (packages.json) in the current working directory to keep track of the dependencies from the npm website that we are using.

$ npm init

Once npm is initialized in a project, we can install the npm packages that we need. The following command show how to install a particular version (13.0.0) of the module named validator.

$ npm install validator@13.0.0

When we install a npm module for the first time, a directory named node_modules is created in the project’s root directory and the module’s code is copied from the npm server to this directory. Note that you should never modify the node_modules directory directory.

We load an npm module in the same manner as loading a Node.js module using require(). Below we load the validator module and then call it’s isEmail() method.

const validator = require('validator')
...
console.log(validator.isEmail('joe@n0code.net')

If you encounter a Node.js project that is missing the node_modules directory but has npm dependencies listed in the packages-lock.json file, then you can install the dependencies by running the following command from the project’s root directory.

$ npm install

Part 12. Printing in Color

Do the challenge using the chalk npm package.

Part 13. Global npm Modules and nodemon

When we install a npm module using npm install ... on the command line, the module is installed locally in the node_modules directory. These modules are accessible in our app’s code.

We can also install a npm package globally. When we do, we can run the application in the package from the command line.

The nodemon package allows the user to automatically rerun an application when the code in the application changes.

We can install a package (e.g. nodemon) globally using the -g flag as shown below.

$ npm install nodemon@2.0.4 -g

To run an application with nodemon, we use nodemon instead of node on the command line. For example, to run an application named app.js with nodemon we would start the application with the following command.

$ nodemon app.js

Now when we change the source code and save the changes, nodemon automatically restarts the program.

File System and Command Line Args (Notes App)

Part 15. Getting Input from Users

We can pass data into a Node.js application using command line arguments. For example, to pass a string Eric from the command line into an application named app.js, we would execute the following command.

$ node app.js Eric

To retrieve the string inside the application we use the array process.argv. The first string (at index 0) in the process.argv array is always the path to the node executable and the second (at index 1) is always the path to the application that is running. All other data is passed in starting at index 2.

So to print the string Eric inside the application we can do something like this.

let name = process.argv[2]
console.log(name)

Part 16. Argument Parsing with Yargs: Part I

The yargs npm package parses command line arguments so we don’t have to. Let’s install yargs.

$ npm i yargs

And load the yargs library into a variable named yargs using require().

const yargs = require('yargs')

We can access the parsed command line arguments using the object in yargs.argv.

console.log(yargs.argv);

Suppose we ran the following command.

$ node app.js add list remove -a -b='one' -c=2 --cyan --green='two'

Then we would see the following object.

{
_: [ 'add', 'list', 'remove'],
a: true,
b: 'one',
c: 2,
cyan: true,
green: 'two',
'$0': 'app.js'
}
  • Notice that all command line arguments that do not begin with – or — are stored in the _ (underscore) array.
  • Arguments starting with hyphens are stored as separate properties (name/value pairs) with the hyphens stripped from the keys.
  • Single hyphens (-) denote single character keys.
  • Double hyphens (–) denote multiple character keys.
  • If an equals sign follows a key, then the value following the equals sign is stored as the value of the property. Otherwise, the value is true.
yargs.command

Yargs has a function named command() that can register a function handler for a specific command line argument, so that if the argument is present on the command line then the function is automatically executed.

In the example below we register an anonymous function for the list command.

yargs.command({
    command: 'list',
    describe: 'List your notes',
    handler: function() {
        console.log('listing your notes') 
    }
})

After setting up command handlers we need to request that yargs parse the command line. We do so by calling yargs.parse().

yargs.parse()
–help and –version

Yargs provides a default help dialog that the user can access using the --help flag. By default it prints two options: –help and –version and prints the descriptions of the commands registered with yargs.command().

By default, yargs also handles the --version flag by printing the version specified in package.json. You can change the version printed by yargs using the following statement.

yargs.version('1.2.3')

Part 17. Argument Parsing with Yargs:
Part II

Yargs.command() also provides a builder property that can be used to specify additional required arguments for a particular command. For example, to add a note, we would need a title and the text of the note. The user would specify these additional arguments using double-hyphenated arguments as shown below.

$ node app.js add --title='Shopping list' --text='apples, oranges, and bananas'

Below we show how the builder property can be used.

yargs.command({
    command: 'add',
    describe: 'add a new note',
    builder: {
        title: {
            describe: 'Note title',
            demandOption: true,
            type: 'string'
        }
    },
    handler: function(argv) {
        console.log('Adding a note: ' + argv.title);
    }
})
  • The description property is printed when --help is issued on the command line.
  • The demandOption property indicates if the additional argument is required or not. By default it is false.
  • The type property indicates the type of value that must accompany the additional argument.

Part 18. Storing Data with JSON

Suppose we have a JavaScript object as shown below.

const book = {
    title: 'Atlas Shrugged',
    author: 'Ayn Rand'
}

We can convert a JavaScript object to a JSON string using JSON.stringify()

const bookJSON = JSON.stringify(book);
console.log(bookJSON);

When we print the JSON string to the console we see the following.

{"title":"Atlas Shrugged","author":"Ayn Rand"}

Notice the book object properties have double quotes and the single quotes in the object were replaced by double quotes.

Given a JSON string, we can convert it to a JavaScript object using JSON.parse(). When we do we will again have access to the properties of the object.

const bookData = JSON.parse(bookJSON)
console.log(bookData.title)              //prints Atlas Shrugged
Reading and Writing Strings to a File

We can read and write strings from and to a file using the fs.readFileSync() and fs.writeFileSync() methods respectively.

The fs.readFileSync() method takes a file name as an argument and returns a data buffer (not a string). So if want to use the string stored in the file we need to retrieve the string from the data buffer using the buffer’s toString() method. In the example below we assume that a JSON string is stored in the file book_data.json.

const dataBuffer = fs.readFileSync('book_data.json')
const bookJSON = dataBuffer.toString()
console.log(bookJSON)

When we want to write a string to a file we can use fs.writeFileSync() which takes the name of a file as the first argument and the string to be written as the second argument. In the example below we assume we have a JSON string stored in the variable bookJSON.

fs.writeFileSync('book_data.json', bookJSON)

Part 18. Adding a Note

We can export multiple values from a JavaScript module by exporting an object. In the JavaScript file below (referred to as notes.js) we export two functions.

const foo = (a) => {
    console.log("foo: " + a)
}

const bar = (a) => {
    console.log("bar: " + a)
}

module.exports = {
    foo: foo,
    bar: bar
};

We can use these two functions in another module by using the dot operator on the object returned by require().

const notes = require('notes')

console.log(notes.foo('test1'))
console.log(notes.bar('test2'))

Part 20. Removing a Note

Complete the challenges.

Part 21. ES6 Aside: Arrow Functions

Arrow Functions don’t bind their own this property but methods defined using the ES6 method definition syntax do. So we can’t do this…

const event = {
    name: 'Birthday Party',
    print: () => console.log(this.name)
};

event.print()

But we can do this…

const event = {
    name: 'Birthday Party',
    print() {
        console.log(this.name)
    }
};

event.print()

Part 22. Refactoring to Use Arrow Functions

Complete the challenge.

Part 23. Listing Notes

Complete the challenge

Part 24. Reading a Note

Complete the challenge

Debugging Node.js (Notes Apps)

Part 26. Debugging Node.js

  • Most basic debugging tool: Useconsole.log() to print the values in variables
  • Node debugger
    • Use debugger in your JavaScript file where you want the program to pause
    • Run Node with the inspect command line option
      • $ node inspect …
      • Use the restart command at the debug> prompt to restart the program.
      • Use ctrl+c twice to terminate the debugger
    • When debugging, open Chrome and navigate to chrome://inspect
      • Add your application folder to the inspection file system (+)
      • Resume script execution
      • View the variables that are in scope in the right hand side pane
      • Use the console to print the value of the variables in scope

Part 27. Error Messages

Use the stack trace to find the location of the error.

Asynchronous Node.js (Weather App)

Part 29. Asynchronous Basics

A synchronous program runs each statement after the previous statement completed.

setTimeout(handler, duration) is an asynchronous function which means that when the JavaScript engine calls setTimeout, it does not pause execution until the timer expires, but rather it continues executing the code after the call to setTimeout, and when the setTimeout timer expires, it then executes the handler.

Part 30. Call Stack, Callback Queue, and
Event Loop

The Call Stack holds all of the functions that are currently running starting with main().

When an event handler is registered with Node, Node waits for the event to occur.

After the event occurs, Node places the event handler (a.k.a. callback function) on the Callback Queue.

When the Event Loop sees that the Call Stack is empty, Node places the callback at the head of the Callback queue onto the Call Stack, which is then run.

No asynchronous function will ever run before the main function completes.

Part 31. Making HTTP Requests

Let’s first start by initializing a new app as an npm project. Note that the -y flag automatically generates the nmp package.json file using the default values.

$ npm init -y

Weather information can be retrieved using the weatherstack.com API. When you create an account you’ll be given an API key. Using the API’s current weather endpoint (api.weatherstack.com/current) you can construct a URL with your API key and the latitude and longitude of a locale. Note that you have to replace YOUR_API_KEY in the URL below with the API key provided to you by weatherstack.com.

http://api.weatherstack.com/current?access_key=YOUR_API_KEY&query=38.44,-79.12

Pugging the URL in a browser will retrieve the current weather data for a weather station near Briery Branch, VA in a JSON string. If we install the JSON Formatter extension in chrome we can see the JSON string in object form. Try it.

We can use the npm’s postman-request package to retrieve the JSON data from weatherstack.com in our app. From the command line let’s install the postman-request package.

$ npm i postman-request

Inside app.js, let’s load the postman-request module using require().

const request = require('postman-request')

We can now use request() to request data from the weatherstack server and register a callback that executes when the data is received. The first argument to request() is an object containing the endpoint URL and the second argument is the callback function that is executed when a response is received. The callback function has two object parameters: error and response. The response object’s body property holds that data that was returned by the weatherstack server.

Below we convert the JSON string received from the server to an object and print the current weather for a weather station near Briery Branch, VA.

const url = 'http://api.weatherstack.com/current?access_key=YOUR_API_KEY&query=38.44,-79.12

request({url: url}, (error, response) => {
    const data = JSON.parse(response.body)
    console.log(data.current)
})

Part 32. Customizing HTTP Requests

The postman-request module has many options.

We can have request() parse the JSON data for us by adding the json: true option to the request() call.

request({url: url, json: true}, (error, response) => {
    console.log('It is currently ' 
+ response.body.current.temperature + ' degrees out and feels '
+ response.body.current.feelslike + ' degrees out') })

To request the temperature data from weatherstack.com be represented in fahrenheit, we can add units=f to the query string.

const url = 'http://api.weatherstack.com/current?access_key=YOUR_API_KEY&query=38.44,-79.12&units=f

Part 33. An HTTP Request Challenge

Mapbox.com offers various location based services including a geocoding service that provides the latitude and longitude data for a given locale like ‘Briery Branch Road’.

Similar to weatherstack.com, when we create a mapbox.com account we are given an API key. The mapbox.com endpoint for forward geocoding is api.mapbox.com/geocoding/v5/mapbox.places. Using the endpoint, a locale, and your API key, you can construct a URL. Again, you’ll need to replace YOUR_API_KEY with the API key provided to you by mapbox.com.

const mapbox_url = 'https://api.mapbox.com/geocoding/v5/mapbox.places/briery%20branch%20road.json?access_token=YOUR_API_KEY'

We can then use request() to get the latitude and longitude for Briery Branch Road. Again we use the json: true option to convert the JSON string returned by the mapbox.com server to an object.

In the example below, mapbox.com returns 5 data sets in the features property that are potential matches for Briery Branch Road. We print the name, latitude, and longitude for the first element in the features array.

request({url: mapbox_url, json: true}, (error, response) => {
    console.log(response.body.features[0].place_name)
    const long = response.body.features[0].center[0]
    const lat = response.body.features[0].center[1]
})

Part 34. Handling Errors

If request() is successful in connecting to the URL provided, the error object that is passed to the callback function will be undefined. If however, request is not able to connect the server (e.g. the user does not have an internet connection), the error object will not be undefined.

We can use and if-else block inside our callback function to gracefully deal with these sorts of errors.

...
    if (error) {
        console.log('Unable to connect to the weather service!')
    } else {
        console.log('It is currently ' 
+ response.body.current.temperature + ' degrees out and feels '
+ response.body.current.feelslike + ' degrees out') } ...

If request() is able to connect to the weatherstack server, but the server cannot complete the request (e.g. the query string was invalid), the response.body.error property will hold information indicating why the server was unable to complete the request.

To handle this we’ll add an additional else if block.

...
    if (error) {
        console.log('Unable to connect to the weather service!')
    } else if (response.body.error) {
        console.log('Unable to find location')
    } else {
        console.log('It is currently ' 
+ response.body.current.temperature
+ ' degrees out and feels '
+ response.body.current.feelslike + ' degrees out') } ...

Note that each API service that we use has its own way of indicating that it was unable to complete the request. For example, if the mapbox server cannot find a locale, the features array will be empty.

Part 35. The Callback Function

Consider the following pattern.  The function named add takes an anonymous function (3rd argument) as an argument and calls the anonymous function after 2 seconds.

const add = (a, b, callback) => {
    setTimeout(()=>{
        callback(a+b)
    }, 2000);
}

add(1,4,(sum) => {
    console.log('sum: ' + sum);
})
    

We’ll use this pattern to get the longitude and latitude values from the mapbox api asynchronously and we have received it, pass it to a callback function that will get the weather at that location from weatherstack.

Part 36. Callback Abstraction

In Parts 31-33 we used two different APIs. The first retrieved the current weather for a location defined by the longitude and latitude. The second retrieved the longitude and latitude for a particular location.

Since both APIs are used using the asynchronous request() call, we need a way to execute both API requests in order, the former after the later has returned. We can do so by creating functions that have callbacks as arguments.

First, we create a geocode module with an exportable function that takes an address and a callback as arguments. When the exportable function is called, after it connects to mapbox, it calls the callback function, passing to it either an error message or the longitude and latitude data that it received from mapbox.

const request = require('postman-request')

const geocode = (address, callback) => {

    const mapbox_url = 'https://api.mapbox.com/geocoding/v5/mapbox.places/' +         encodeURIComponent(address) + '.json? access_token=pk.eyJ1IjoiZXJpYzE5NjgiLCJhIjoiY2tic3BxaGxnMDJhejJwcnFwdG5ybzlpdCJ9.6UOuKmCY7rjpY0If62xHzg'

    request({url: mapbox_url, json: true}, (error, response) => {
        if (error) {
            callback('Unable to connect to the server', undefined)
        }
        else if (response.body.features.length == 0) {
            callback('Unable to find location', undefined)
        }
        else {
            callback(undefined, {
                location: response.body.features[0].place_name,
                long: response.body.features[0].center[0],
                lat: response.body.features[0].center[1]
            })
        }
    })
}

module.exports = geocode

Notice the use of encodeURIComponent(address) in the code above.

We can then import geocode() in app.js and use it as shown below.

const geocode = require('./geocode')

geocode('Briery Branch Road', (error, data) => {
    if (error) {
        console.log(error)
    }
    else {
        console.log('location: ' + data.location)
console.log('lat: ' + data.lat + ', long: ' + data.long); } })

In the next section we write a similar function for the weatherstack function.

Part 37: Callback Abstraction Challenge

const request = require('postman-request')

const forecast = (long, lat, callback) => {

    const ws_url = 'http://api.weatherstack.com/current?access_key=0fb89af92df9e7d38c98a6719cd6d1ea&query=' + lat + ',' + long + '&units=f'

    request({url: ws_url, json: true}, (error, response) => {
        if (error) {
            callback('Unable to connect to the weatherstack server', undefined)
        }
        else if (response.body.error) {
            callback('Unable to get weather for specified locale', undefined)
        }
        else {
            callback(undefined, {
                temperature: response.body.current.temperature,
                feelslike: response.body.current.feelslike
            })
        }
    })
}

module.exports = forecast

Part 38: Callback Chaining

So that the forecast() function is called after geocode(), we place the call to forecast() in the callback for geocode().

const geocode = require('./geocode')
const forecast = require('./forecast')

geocode(place, (error, data1) => {
    if (error) {
        return console.log(error)
    }

    forecast(data1.long, data1.lat, (error, data2) => {
        if (error) {
            return console.log(error)
        }

        console.log('It is currently ' + data2.temperature + ' degrees out and feels ' + data2.feelslike + ' degrees out')
    })
})

Part 39. ES6 Aside: Object Property
Shorthand and Destructuring

Object Property Shorthand

We typically define objects as follows.

const name = 'Andrew'
const userAge = 27

const user = {
name: name,
age: userAge,
location: 'Virginia'
}

We can use a shorthand method of defining a property of an object when the value of the property comes from a variable with the same name as the property.

const name = 'Andrew'
const userAge = 27

const user = {
name,
age: userAge,
location: 'Virginia'
}

The shorthand is to only write the property name.  The property is assigned the value of the variable with the same name automatically.

Object Destructuring

When we have an object, we often want a standalone variables that has the value of one of the object’s properties.  We could declare a variable and initialize it with the value of the corresponding object property as shown below.

const product = {
label: 'Book",   
price: 3
}

const label = product.label
const price = product.price

 With destructuring we have a more succinct syntax. 

const {label, price} = product

console.log('label: ' + label);
console.log('price: ' + price);

In the code above, two new const variables are defined named label and price and their values are taken from the product object’s properties of the same name. 

If you want to create variables that have names that differ from the object’s property names, you list the new name after the property name within the curly braces as shown below.

const {label:productLabel, price} = product

console.log('label: ' + productLabel);
console.log('price: ' + price);

We can provide default values for properties that don’t exist in the object using the syntax below.

const {label:productLabel, price, rating = 5} = product

console.log('rating: " + rating)

When defining a function that takes an object as an argument, we can use destructuring to get access to individual properties of the object as parameters.

const print = ({label, price}) => {
console.log('label: ' + label)
console.log('price: ' + price)
})

print(product);

40. Destructuring and Property Shorthand Challenge

Please try on your own.

41. Bonus: HTTP Requests Without a Library