Week 2
Node Modules & Express

Quiz 1: JavaScript and Node.js Fundamentals 15 mins

There will be a quiz today. It will be worth 2% of your final grade.

Agenda

  • AMA (15 mins)
  • Quiz (15 mins)
  • Break (10 mins)
  • Node Module System (40 mins)
  • Node HTTP Server (20 mins)
  • Break (10 mins)
  • Node Package Managment (5 mins)
  • Introduction to Express (45 mins)

Node Modules

Global Scope

In the browser the top level object is called window. In node, each module has it's own private "top level" variable scope.

  • Every JavaScript file is a module
  • Every module has its own scope
    • variables and functions defined in a module (js file) are private to that module unless they are explicitly exported.
    • public interface v.s. implementation details
    • console.log(module) to see the default exports property. It is an empty object that we can add properties into.
  • There are also a handful of globally available objects, like process that give us access to the runtime environment.

Let's look at some simple examples

// module-a.js
const foo = function() {}
const bar = function() {}
const baz = function() {
  console.log('This is a private function')
}

module.exports.foo = foo
module.exports.bar = bar

Access the exposed functions or values in other modules via the require() function.

// module-b.js
const utils = require('./module-a.js')

utils.foo() // call the function foo from module-a
utils.bar() // call the function bar from module-a
utils.baz() // this will cause an error because the function baz was not exported

We can also use destructuring assignment to just grab the exported function that we need.

// module-b.js
const {foo} = require('./module-a.js')

foo() // call the function foo from module-a

We can also reassign exports to be a single function.





 

// module-a.js
const foo = function() {} // exported
const bar = function() {} // not exported, private to this module

module.exports = foo

Then in module-b we would access it like this, without destructuring ...


 



// module-b.js
const foo = require('./module-a.js')

foo() // call the function foo from module-a

This would also work to export a static value.


 




// module-a.js
const foo = 'some string' // exported
const bar = function() {} // not exported, private to this module

module.exports = foo



 

// module-b.js
const foo = require('./module-a.js')

console.log(foo) // 'some string'

This ability to reassign the value of module.exports means that in the earlier example, we could have exported both foo and bar like this ...

// module-a.js
const foo = function() {}
const bar = function() {}

module.exports = {foo, bar}

TIP

ES6+ allows us to use a shortcut when creating object properties with the same name as the variable that holds the value that we are assigning. So, the above example is the same as writing module.exports = {foo: foo, bar: bar}

IIFE Wrapper

There are some default objects that are automatically made available in every node.js module:

  • __dirname
  • __filename
  • exports
  • module
  • require()

If we peaked under the hood of Node's implementation, we would see that each module gets wrapped in an Immediately Invoked Function Expression (IIFE) at runtime and that function has the signature of

;(function(exports, require, module, __filename, __dirname) {
  // your module code
})()

This both limits the scope of each module and injects the standard arguments, including the require function, into that scope.

Standard Modules

The full list of built-in node modules is in the Node.js documentation, but these are some of the more commonly used examples.

  • fs
  • http
  • os
  • path
  • process
  • queryString
  • stream

Examples

Lets use the fs.readFile() method to read the contents of our students.json file from last week. Then in the callback function we will use JSON.parse() to convert the JSON string to a native JavaScript data structure.

const fs = require('fs')
let students

fs.readFile('./students.json', (err, data) => {
  if (err) {
    console.log(err)
  }
  students = JSON.parse(data)
  console.log(students)
})

This is simple enough, but if our application logic depends on the students data, then we have to nest all of that inside our callback function, or make it a blocking synchronous operation with fs.readFileSync().

Try this

Move the console.log() statement outside of and below the fs.readFile() code block. What does it print out on the console?

However, Node.js is starting to implement Promise based versions of it's core modules and we can now use async/await syntax like this ...

const fs = require('fs').promises

async function getStudents() {
  try {
    const fileHandle = await fs.open('./students.json', 'r')
    const jsonData = await fileHandle.readFile()
    return JSON.parse(jsonData)
  } catch (err) {
    console.log(err)
  }
}

getStudents().then(console.log)

Event Emitters

In order to interact with the event loop, some modules need to be able to emit an event (sometimes also called 'raise an event'). These modules should be defined as a class that extends (or inherits from) Node's EventEmitter class.

Base EventEmitter Class

const EventEmitter = require('events')
const myEmitter = new EventEmitter()

// define an event listener
myEmitter.on('event-name', callbackFunction)

// emit an event with an optional data payload
myEmitter.emit('event-name', payload)

Extends EventEmitter

// Notification.js
const EventEmitter = require('events')

class Notification extends EventEmitter {
  constructor(channel, message) {
    this.channel = channel
    this.message = message
  }
  someFunction() {}
  send() {
    // do the real work

    // then raise an event
    this.emit(this.channel, this.message)
  }
}

module.exports = Notification

We would then use our Notify class like this

const Notification = require('./Notification.js')
const sms = new Notification('+16135551212', 'This is a test')

sms.send()

EX2-1 HTTP Server

An example of a built-in module that extends EventEmitter, is the http module.

We will use one of the core Node modules – http – to create a simple web server.

Lets create a new project folder called week2. Then cd into that folder and open our code editor.

mkdir week2

cd week2

code .

Create a new file called nodeServer.js. We will build out our plain node http server example here.

  1. Use the require() function to load the http module into a local variable.
'use strict'

const http = require('http')
  1. Use the createServer() method to instantiate a new http.Server object. This method takes an optional request handler callback function. For each request that the http server receives, node passes two core objects to the request handler function: req (request), and res (response).
const server = http.createServer((req, res) => {
  res.write('Hello world from Node.js')
  res.end()
})

TIP

The Server object returned by createServer is an EventEmitter, and the optional callback function is just shorthand for creating a server object and then adding a event listener later.

const server = http.createServer()
server.on('request', (req, res) => {
  // evaluate request and send response
})
  1. Lastly, we need to tell the server what port to listen on for incoming HTTP requests. We will use port 3030 for our example. The listen() method takes two arguments: the port number and a callback function which in turn receives an error object. The error object will be null if the listen method executed successfully.
server.listen(3030, err => {
  if (err) {
    return console.error('something bad happened', err)
  }
  console.log(`Server listening on port 3030 ...`)
})

OK, lets run it in the terminal.

node nodeServer.js

... and try it in the browser at http://localhost:3030

Hello world from Node.js

Route handling

Augment the request handler function to provide different responses on different URL routes. We will extract the request handler into a separate function and have it respond to the /api route with a JSON message.

const requestHandler = (req, res) => {
  if (req.url === '/api') {
    const data = {message: 'Hello world from Node.js'}
    res.statusCode = 200
    res.setHeader('Content-Type', 'application/json')
    res.write(JSON.stringify({data})) // shorthand for {data: data}
    res.end()
  } else {
    // default response if no other route matched
    res.write('Hello world from Node.js')
    res.end()
  }
}

const server = http.createServer(requestHandler)

JSON:API Best practice

A JSON object MUST be at the root of every JSON:API request and response containing data. This object defines a document’s “top level”.

A document MUST contain at least one of the following top-level members:

  • data: the document’s “primary data”
  • errors: an array of error objects
  • meta: a meta object that contains non-standard meta-information.

The members data and errors MUST NOT coexist in the same document.

Stop and restart the server in the terminal. Remember CTL + c stops the currently running command in the terminal. Now check it in the browser at http://localhost:3030/api

screen shot of API data payload

TIP

The JSON data payload is nicely formatted because I am using a Chrome plugin called JSONView, which you can install from the Google Chrome Web Store

EX2-2 Hello from Express

NPM Init

  1. Initialize the current directory (week2) as an NPM project folder using the npm init command in the terminal.
npm init

It will respond with the following message ...

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 json` 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.

... and then ask some basic setup questions one at a time. Just press enter to accept the default value (in brackets) or leave it blank. Otherwise add the values that I have added below. Of course, please use your own name and college email, not mine.

package name: (week2) hello-express

version: (1.0.0)

description: My first Express server

entry point: (nodeServer.js) app.js

test command:

git repository:

keywords:

author: Robert McKenney <mckennr@algonquincollege.com>

license: (ISC)

NPM will now display a confirmation of the details that it is about to write in the package.json file. Yours should look similar to this.

About to write to /Users/rlm/Code/algonquin/mad9124/demos/week2/package.json:

{
  "name": "hello-express",
  "version": "1.0.0",
  "description": "My first Express server",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Robert McKenney <mckennr@algonquincollege.com>",
  "license": "ISC"
}

Is this OK? (yes)

Press enter to accept and complete.

  1. Install the Express framework using NPM.
npm install express

When it finishes you should see a message like this ...

npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN hello-express@1.0.0 No repository field.

+ express@4.17.1
added 50 packages from 37 contributors and audited 126 packages in 2.16s
found 0 vulnerabilities

... and the package.json file should have been updated to include a dependencies key

"dependencies": {
  "express": "^4.17.1"
}

Express App

We are going to start by replicating the earlier nodeServer.js example but using the Express library. Express uses Node's core HTTP module under the hood, but simplifies many of the repetitive coding tasks.

In your code editor, create a new file called app.js. At the top we will import the Express library's constructor function and instantiate a new express app.

'use strict'

const express = require('express')
const app = express()

Basic Route

We need to create a server root route that returns "Hello from Express!".

Here is one of the big differences with Express. We aren't stuck with a single request handler function with complicated if/else logic to handle different routes. Express exposes methods on the app object that correspond to the various HTTP verbs that allow us to construct our routes in a cleaner, more declarative fashion.

These route methods take a URI path to match and a request handler function.

app.get('/', (request, response) => {
  response.send('Hello from Express!')
})

Like Node's http module, Express defaults the response status code to 200 unless we override it using the response.code property. e.g.

response.code = 201 // created

Now invoke the listen() method to tell our app to listen on port 3030. This method creates a fully configured instance of the same Node http.Server object that our earlier example created, but with less boilerplate (repetitive) code.

const port = 3030 // this should go near the top of the file

app.listen(port, err => {
  if (err) return console.log('something bad happened', err)
  console.log(`The server is listening on ${port}`)
})

We are ready to run it in the terminal with node app.js, and check it in the browser at http://localhost:3030. It should look identical to the nodeServer.js version.

screenshot of the default route payload

JSON API Route

Now let's add that /api route that returns a JSON response.

// just above the line containing app.listen()

app.get('/api', (request, response) => {
  response.send({
    data: {
      message: 'Hello from Express!'
    }
  })
})

If we check that in the browser now, you'll see that Express recognizes that we are sending an object instead of a string and automatically sets the correct header and stringified payload.

screenshot of the api route payload screenshot of the header details in Chrome dev tools

Let return a more complex payload

First add a new hard coded array of car objects.

const cars = [
  {id: 1, make: 'Tesla', model: 'S', colour: 'Black'},
  {id: 2, make: 'Tesla', model: '3', colour: 'Red'},
  {id: 3, make: 'Tesla', model: 'X', colour: 'Silver'},
  {id: 4, make: 'Tesla', model: 'Y', colour: 'Chestnut Brown'}
]

Now add a new /api/cars route that returns the array of car objects.

app.get('/api/cars', (request, response) => {
  response.send({data: cars})
})

Load the cars array from a module

  1. create a new module file called cars.js
  2. copy the array declaration from above into that new file
  3. export the array
  4. replace the hard coded array in app.js with a require statement to get cars from the new module

Git'er done

Initialize the current (week2) project folder with git

git init

Add a new file in the current project folder (week2). Name it .gitignore and don't forget the leading period in the name - it is important. Add these two lines inside the .gitignore file.

.DS_Store
node_modules/

Reminder

The .gitignore file tells git to exclude certain files and folders from the source control repository. In this case, the node_modules folder includes all of the third-party libraries that NPM installed for us.

This is not our code. It does not belong in our repo.

We record our dependencies in the package.json file, and can have NPM re/install them for us at any time by running the npm install command.

Make a commit with all of the files from today's class

git add .
git commit -m "Completed week2 in-class exercises"

Create a new private repo on GitHub named mad9124-week2-userid (please substitute your college userid. i.e. mine would look like mad9124-week2-mckennr)

screenshot create GitHub repo example

Link your new empty GitHub repo as a remote on your local repo with the git remote add origin command and then sync it with the git push command. GitHub will display the commands with the correct link to your new repo. Use the "push an existing repository from the command line" instructions.

screenshot

Because your repo is private, you will have to add me as a collaborator so that I can see your code. My GitHub userid is rlmckenney. Go to the [settings] page and then click on the [collaborators] tab on the left. Add my user name in the box at the bottom.

screenshot

Don't forget to submit the URL to your GitHub repo on BrightSpace

For next week

Before next week's class, please review these additional online resources.

The Event Loop

Node's HTTP module

JavaScript Promises

JavaScript Classes and Prototypes

Quiz

There will be a short quiz next class. The questions could come from any of the material referenced above.

Last Updated: 1/5/2020, 5:05:07 PM