4-1 Node.js primer

This lab goes through the basics of the Node.js platform.

Quick start

Node.js (https://nodejs.org) is a platform built on Chrome’s JavaScript runtime V8 for easily building fast, scalable network applications. Node.js supports the same basic JavaScript programming language as web browsers. As the first example, let’s run the following program p101.mjs in both browser and Node. (Follow instruction to install Node.)

// p101.mjs
// return the largest number in an array
// assume at least 1 entry, and all entries are numbers
function largest (A) {
  let big=A[0];
  for (let i=1; i<A.length; i++) {
  	if (big<A[i]) big=A[i];
  }
  return big;
}

let N = [ 3, 7, 6, 8, 2, 5 ];

console.log(`The largest is ${largest(N)}`);

We save JavaScript programs in files with extension *.mjs instead of *.js in this chapter. The extension informs Node.js that this file is a ES module, instead of the default of Common JS module. This allows us to use the modern syntax import to import modules. Instead of use the mjs extension, you can also set the default module type in the type attribute of the package.json config file.

The JavaScript engine provides some common features in both Node and browsers:

  • JavaScript language features: control flow, variables, data structure, defining class, functions, regular expression, etc
  • Some global objects and functions:
    • console.log()
    • timers: setInterval(), setTimeout()
    • Date class: let now = new Date()
    • JSON processing: JSON.parse(), JSON.stringify()

But Node misses some features specific to the browser platform, e.g.

  • document tree (i.e. DOM tree)
    • a Node program generally does not involve an HTML document
    • no interface and mouse or keyboard events
  • and other APIs in the Web API

Event loop in Node

Similar to JavaScript runtime in browser, Node includes an event loop that handles events. The event loop continues until all events are handled and there are no future events. Some common cases that trigger events are:

  • timer: when time is up, call a function
  • file I/O, network I/O, etc: When some data is ready to read, or some I/O operations finish, the Node runtime triggers an event and call an event handler
  • some objects can generate events
  • system events, e.g. Ctrl-C interrupt

Trace the execution of the following program.

// p102.mjs
// countdown from 10
let N = 10;

function tick() {
  if (N<=0) {
    console.log('Time\'s up!');
    clearInterval(timer);
  } else {
    console.log(N); N--;
  }
}

// this starts the timer
const timer = setInterval(tick, 1000);

// event loop goes here ...

At any time, Node runs at most 1 event handler. If there are no pending events, the event loop blocks and waits for any future events. If Node determines that there are no more future events, it quits.

Modules

Node also support the new standard of JavaScript module known as ES module (online reference). This allows you to import new functionalities to the Node platform. The Node platform comes with some built-in modules (which are installed together with Node). See the online reference for a list of the built-in modules.

Many modules in Node provide a default export, and usually the default export is an object that encapsulate functions or classes of the module. To import this kind of modules, use import variableName from 'moduleName'. This function returns an object that represents the module in your program. You can assign this to a variable of any convenient name.

The following example demonstrates how to use another built-in module os to find some basic hardware information of your computer.

// p103.mjs
// show basic info about CPU and memory
import os from 'os';

let cpus = os.cpus();
console.log(`CPU: ${cpus[0].model}, ${cpus.length} core`);
console.log(`Total memory: ${os.totalmem()/1024**3}G`);

NPM modules

Node.js comes with many built-in modules that cover various network services. But usually programmers take advantage of many high-quality third-party JavaScript libraries. These libraries are packed as packages, and Node.js used the built-in NPM command line tool to download and install packages.

By default, the NPM command line tool downloads packages from https://npmjs.com/, which is the largest registry of JavaScript packages. For example, the systeminformation package provides a lot more information than the Node built-in module os.

// p104.mjs
import si from 'systeminformation';

si.cpu()
  .then(data => console.log(data));

In order to run the above example, you have to first download the systeminformation module from npmjs.com with the following command. This command creates a folder ./node_modules and install downloaded packages locally in the current project.

$ npm install systeminformation
# or, a shorthand notation 
$ npm i systeminformation

After installing the package, you can now run example p104.mjs.

$ node p104.mjs

As we mentioned in previous lectures, a Node project is a folder that contains the JavaScript programs, JavaScript libraries / modules and other assets (e.g. HTML, images) for an application.

Instead of installing packages locally in a project, you can also install packages globally with npm install -g packageName. However, we’ll not do this in this course.

Package.json

Remembering and installing the dependencies of a Node application manually is error-prone. Dependencies of an application refer to the packages that must be installed properly before this application and run successfully. Fortunately, Node.js already supports a configuration file at the root of an application project folder called package.json. Among other functionality, package.json contains a property dependencies that list the packages required by the application. Run this command to create a package.json at the current folder.

$ npm init -y

You’ll see the following data in the package.json generated.

{
  "name": "chap4-1",
  "version": "1.0.0",
  "dependencies": {
    "systeminformation": "^5.9.2"
  },
  // other properties omitted for brevity
}  

From now on, when you install more packages locally, it is assumed that the packages are new dependency of the current applicatoin, and npm will update package.json to include the dependency. For example, install the following package, and check the update in dependencies in package.json.

$ npm install prompts

When you pack your Node application for sharing (e.g. upload to npmjs.org), deployment (i.e. to run in production environment), or hand in your assignment, DON’T include the node_modules folder. There are several reasons.

  • The folder usually contains a lot of files and occupy large disk space.
  • When a user installs your application in their machine, they may want to use a more recent patch of some dependencies.
  • Some packages include platform-specific binaries (e.g. libraries written in C).

With the dependencies listed in package.json, one can install them easily with the following command.

$ npm install

Semantic versioning

In package.json, you might notice a version number after each mention of package.

  "dependencies": {
    "prompts": "^2.4.1",
    "systeminformation": "^5.9.2"
  },

This software versioning convention, called semantic versioning (online reference), is an important part of package management in Node / NPM. An explanation at the official page of https://semver.org/ is quoted below.

Given a version number MAJOR.Minor.patch, increment the:

  • MAJOR version when you make incompatible API changes,
  • Minor version when you add functionality in a backwards compatible manner, and
  • patch version when you make backwards compatible bug fixes.

Check the Version History of the systeminformation package and explain the meaning of version updates.

The dependencies section of package.json adds the symbol ^ before the version number. For version >= 1.0.0, the caret range ^a.b.c means a version >= the given version a.b.c, but smaller than a+1.0.0. For example, "systeminformation": "^5.9.2" means that, in the future, npm install can install the following versions of the package because they are considered as compatible with the application: 5.9.2, 5.9.3, 5.10.0, 5.10.1, 5.23.45. However, versions before 5.9.2 are not acceptable. (They may have less features, or they include a bug that has been fixed in 5.9.2. Moreover, version 6.x.y is not acceptable because they will introduce breaking changes. For other possibilities of specifying usable versions, refer to the online reference.

Downloadable example

The example source files are available here.