A Student's Guide to Software Engineering Tools & Techniques »

Introduction to Node

Authors: Rachael Sim, Ang Ze Yu
Reviewers: Shradheya Thakre, Nicholas Chua, Sam Yong, Daryl Tan, Iskandar Zulkarnaien, Tan Yuan Hong

This chapter assumes that the reader is familiar with JavaScript and asynchronous programming. If you are not familiar with asynchronous programming, a good resource to checkout is the asynchronous programming section of the You Don't Know JS guide as asynchronous programming is key in Node.

What is Node?

Node is a JavaScript runtime built on Chrome’s V8 JavaScript engine. It uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. --https://nodejs.org

Node is mostly used in back-end and server side scenarios. For example, LinkedIn mobile app backend is built on Node and Uber built its massive matching system between customers and drivers on Node. However, Node can also be used in the front-end to automate tasks such as building, testing, pre and post processing code.

Why use Node?

Now that we know what Node is, let us look at some benefits it has to offer.

Benefit 1: Easy to Get Started

To install Node, simply download the installer from the official Node website based on your OS and run it. This installs both Node and npm. Npm is a tool which will help you to search, install and manage node packages, which will be further explored later.

The following example demonstrates how a Node application imports required modules, creates a server to listen for a client's request, and then sends back a 'Hello World' response.

A Simple Hello World Server from codeburst

Create a file server.js with following content:

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer(function(req, res) {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World');
});
server.listen(port, hostname, function() {
  console.log('Server running at http://' + hostname + ':' + port + '/');
});

After you save the file, you can execute it from your terminal:

$ node server.js
Server running at http://127.0.0.1:3000/

To test the server, open a browser tab and navigate to http://localhost:3000/. You should see 'Hello world'.

Read on to find out about how to incorporate external dependencies, manage them and use Node's module system to organize your code.

Benefit 2: Avoid Synchronization Problems and Overheads

Illustration of node's event loop from these slides

Node is designed to be event-driven. When an e.g. when a I/O operation is complete, or a timer firesevent occurs (Operation Complete above), the event handler previously registered with the event is enqueued to be run by the where JavaScript code is executedevent loop (Trigger Callback), which is single-threaded.

This means that we can avoid thread overheads and synchronization problems such as a situation where some threads are blocked due to needing access to the same locksdeadlocks and a situation where multiple operations by different threads are performed on one resource in indeterministic order, potentially causing unexpected changes to the resourcerace conditions.

Benefit 3: Fast for I/O Intensive Programs

I/O requests made in other languages such as Python or Java are typically blocking, which means the program remains idle (in the same thread) until the I/O operation completes.

In contrast, node allows for a non-blocking I/O model with its event-driven structure.

I/O requests are delegated to other systems (e.g. file system and databases). While an I/O operation is incomplete, the event loop can still process subsequent requests. When the I/O request is complete, the handler registered with the request is then scheduled to be executed on the event loop.

Benefit 4: Use JavaScript for Both Front and Back-end Development

Using Node for back-end development makes it possible to share common code functionality between the front-end and back-end, which leads to less code maintainence. Since the same code base is shared, it may hence also lead to a more multidisciplinary team that is familiar with both front and back-end development, reducing the potential communication overhead involved.

Benefit 5: Easy Dependency Management With npm

Node Package Manager (npm) is used to

  • Search for node packages online
  • Install node packages from the command line, manage versions and dependencies effectively and easily

Anyone who wants to use your project would only need to run the following command in the command shell to load the project dependencies.

$ npm install

This command will locate the package.json file (a file that contains all metadata information about a Node application) in the root directory and install all the dependencies specified in it.

A basic package.json has the following structure.

{
  "name": "folder_name",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": ["promise", "lock"],
  "author": "",
  "license": "ISC"
}

The name and a version forms a unique identifier for the package. When a package is updated, the version number must be updated. A good description string and keywords helps others discover your package.

The dependencies property specifies dependencies needed in production while the devDependencies property specifies dependencies needed in development.

{
  "dependencies": {
    "express": "~3.0.1",
    "sequelize":"latest",
    "bluebird": "^3.4.7",
    "angular":"latest",
  },
  "devDependencies": {
    "eslint": "^4.16.0",
    "eslint-config-airbnb-base": "^12.1.0",
    "eslint-plugin-import": "^2.8.0"
  }
}

The dependencies property maps to an object that has the name and Version Range It is common to find carets (^) and tildes (~) in the version range.
This is part of npm's powerful version specification system, which allows users and developers of the package to keep its dependencies compatible.
version range
for each dependency. It is important to specify an appropriate version range to ensure consistency and so that users and developers will have compatible dependencies. For instance, using the latest version of a dependency may introduce breaking changes from API deprecation, making debugging harder.

It is also possible and easier to install a new dependency and update package.json directly from the command line with

$ npm install <package_name>

Once a dependency is installed, the package's code will be added to the local /node_modules folder.

The module section describes how to import packages in your code.

Benefit 6: Versatile npm scripts

package.json also contains a scripts property, which allows specifying various common commands to be run.

For example, inside your package.json, you might have

{
  "scripts": {
      "build": "node app.js",
      "lint": "eslint **/*.js",
      "lint-fix": "eslint --fix **/*.js"
  }
}

Running npm run build in the command shell will execute node app.js and similarly npm run lint-fix will fix your linting errors in your JavaScript files.

Apart from acting as See here for some common use cases of npm scripts!shortcuts to commonly used commands, this also sets up and encourages a consistent development and build workflow in the project.

Benefit 7: Easy to Reuse Code From Others

Node's module system allows you to include other JavaScript files and thus makes it easy to reuse external libraries and organize your code into separate parts with limited responsibilities.

Node comes bundled with useful core modules such as the fs (file system) module which includes classes, methods and events to deal with file I/O operations and the https module which helps Node to transfer data over HTTP. There are also many useful and well-tested modules maintained by the community and external developers such as A library providing various useful wrapper functions over JavaScript's promisesBluebird on the largest ecosystem of open source libraries in the world!npm.

This makes development a breeze -- if a specific functionality has a large development overhead, you could look to such modules to speed things up.

Importing modules is easy - simply use the require() function and provide the module identifier or the file path.

const https = require('https'); // import a core module
const Promise = require('bluebird'); // import a non-core module

Node will first check if the module identifier passed to require is a core module or a relative path. If so, it will return the core module or the value of module.exports in the specified file path's code. Otherwise, Node will attempt to load the module from the node_modules folder in the parent directory of the current module.

Benefit 8: Support for Better Code Organization

With Node's module system, you can create separate modules in your codebase such that each is focused on a single functionality. This makes your code more maintainable and testable.

For example, in a parser module, you could export the Parser constructor in parser.js like so.

function Parser(options) {
  this._options = options || {};
}

Parser.prototype.parse = function (content) {
  ...
}
module.exports = Parser; // override the exports object

Elsewhere, the Parser constructor would then be imported as such, allowing Parser objects to be created in other modules.

const parser = require('../parser') // import content from parser.js based on relative file path. The js extension is assumed and can be excluded.

const content = 'some content';
const newParser = new Parser();
newParser.parse(content);

Use Cases

Node is good for:

  • Processing high volumes of I/O-bound requests. A single instance of a Node server will be more efficient and can serve more requests with the same hardware than most other servers, due to its event driven architecture. This makes a node server faster and more scalable.
  • Real time applications where you have to process a large volume of requests with little delay. This includes instant messaging apps and collaborative editing apps where you can watch the document being modified live such as Trello and Google Docs. Node is a good choice as it can handle multiple client requests even while waiting for responses.
  • Single-page applications where a lot of processing and rendering is done on the client's side and the backend server only needs to provide a simple API. Node can process many requests with low response times. In addition, you can reuse and share e.g. validation logic for user inputsJavaScript code between the client and server.

However, Node is not suitable for

  • CPU-intensive jobs. Recall that event loop is single-threaded. If the thread is busy doing CPU-heavy operations, it will not be able to process incoming requests timely. While it does have some amount of multi-threading support, being a JavaScript runtime, its performance in such cases still trails behind alternatives such as Python which supports threads natively.

Resources