Node.js Cheatsheet [Part 1]

Table of Contents

Introduction

Node.js (Node) is an open source runtime environment backed by a huge developer community. It is often used to prototype web services and backends. Using Node, developers can reuse front-end skills and libraries for the backend. This post is a cheatsheet of common Node tools, commands, concepts, and techniques.

Setup (Node, NVM, and NPM)

Node Version Manager (NVM)

Node evolves quickly and sometimes new releases break backward compatibility. Thus, you may need to maintain and switch between different versions. The Node Version Manager (NVM) comes to the rescue. Like Ruby’s rvm and Python’s pyenv, NVM allows us to work with multiple Node environments. You can install it from here.

The following commands list all available versions:

1
2
3
4
5
# List all locally installed Node-s
nvm ls

# List all available node releases
nvm ls-remote

Here is how to install new Node versions:

1
2
3
4
5
6
7
8
# Install the latest long term support (LTS) version
nvm install --lts

# Install the latest release (may not be LTS)
nvm install node

# Install a specific version
nvm install 7.0.0

When installing a new Node version, you may want to migrate existing global Node packages. This will ensure they’re compatible wit the new version. To do so, use the --reinstall-packages-from=node option:

1
2
# Installs 6.9.0 and migrates global packages
nvm install 6.9.0 --reinstall-packages-from=node

Switching to another installed version goes like this:

1
nvm use 7.4.0

The Node Package Manager (NPM)

NPM is Node’s default package manager and is similar to Ruby’s Bundler and Python’s PIP. It comes preinstalled with Node. To create a new project in a folder:

1
npm init

This will create a package.json file which contains all project dependecies and metadata. It can also include a scripts section which predefines command aliases. Here is an example of a package.json file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "name": "project-name",
  "version": "1.0.0",
  "description": "Example project",
  "main": "server.js",
  "scripts": {
    "test": "echo \"We do not have tests yet!\" && exit 1"
  },
  "author": "me",
  "license": "ISC",
  "dependencies": {
    "request": "^2.60.0",
    "yargs": "^3.15.0"
  },
  "devDependencies": {
    "babel-cli": "^6.22.2"
  }
}

Given this sample, we can run the test command alias as follows:

1
npm test

You can modify the package.json file manually, or add new dependencies via the npm install command with the --save flag:

1
2
3
4
5
6
7
# Install the latest version of the request package 
# and save the dependency in package.json 
npm install request --save

# Install a specific version of the
# package and save the dependency in package.json 
npm install request@2.60.0 --save

We can also install a library as a “dev” dependency with the --save-dev flag. This is useful for tools which we won’t need in production – e.g. testing libraries. Such dependencies are defined in the devDependencies section of the packages.json.

1
npm install babel-cli --save-dev 

All modules are stored in the ./node_modules folder and it should be in your .gitignore. You can reinstall all modules with: npm install.

NPM alllows the installation of global packages. They are not placed in ./node_modules and are shared across all Node environments. In general, you should avoid global packages except for global utilities like debuggers, compilers, and performance monitoring tools. The -g flag denotes that a package is installed globally:

1
2
# Install nodemon globally
npm install nodemon -g

Run, Debug, and Reload

A Node interpreter (REPL) can be started with the node command. This opens up a REPL for running Node commands interactively. To quit we need to type .exit:

1
2
3
4
5
> node # start a Node REPL
> console.log('Hey node!');
Hey node!
undefined
> .exit //Quit

Within a Node project folder, you can execute a Node script, which is a JavaScript file. To debug, use the debug parameter, which starts the script in debug mode. Then you can step through the code via the continue (c), step over/next(n), and step into (s) commands. The repl command allows you to check the values of variables and expressions in the respective context. Breakpoints are put directly in the code as debugger statements:

1
2
3
4
5
# Run a file called script.js in a Node proj folder
node script.js

# Run script.js in debug mode
node debug script.js

Node does not automatically refresh/reload upon code changes and needs to be restarted. Hence, it is convenient to use the nodemon tool which automatically reloads the Node environment. First you need to install it globally:

1
npm install nodemon -g

Then you can use it in place of the node command:

1
2
# Runs server.js and reloads on change
nodemon server.js

Module System

Node does not have logical modular constructs like packages or namespaces. Modules are either files in the project folder, or external NPM packages. The latest version of Node does not natively support the ES6 import and export functionalities. To use them, you need to set-up Babel compilation. Furthermore, most existing JavaScript code uses pre-ES6 syntax. Thus, we will overview both ways of working with modules.

Pre-ES6 modules

Before ES6, modules could export a single variable called module.exports. Client modules would use the require function to load its value:

For example, we could define a module in a file ./friends.js:

1
2
3
4
5
6
7
8
let johny = { name: 'johny' };
let mary = { name: 'mary' };

// This is what client modules will see
// when they include/require this module
module.exports = {
  friends: [johny, mary]
};

Now lets import/include friends.js and a 3rd party module/package moment:

1
2
3
4
5
6
// Load the moment library - installed with  NPM
let moment = require('moment');

// Load the friends.js module. You need to specify its relative path.
// The value of "myFriends" will be equal to the above "module.exports"
let myFriends = require ('./friends.js');

ES6 modules

In ES6, a module can export multiple elements with the export command. A module can optionally have a default export, which is automatically imported when you include the module:

1
2
3
4
5
6
7
8
9
10
11
12
13
// You can export at the time of definition
export const exportedVar = { name: 'I am exported' };
export function exportedFunc() { 
  console.log('I am exported'); 
};

// You can export after definition
const exportMeLater = { name: 'Will be exported' };
export { exportMeLater };

// You can have a single default export
const defaultExport = { name: 'I am a default export' };
export default defaultExport;

External modules are loaded via the import command which has several variations. Assuming the previous example is in a file called importExample.js, we could work with it as follows:

1
2
3
// The default export does not need to be in "{}". The named (i.e. non-default) 
// exports need to be and you can cherry-pick what you import.
import defaultExport { exportedVar, exportMeLater } from './importExample.js'

Environment Variables

Application configuration is often provided in the form of environment variables. Examples of such configuration are: environment identifier (e.g. dev/staging/prod) and database connection url. In Node, environment variables can be accessed through the proces.env object. For example:

1
2
3
4
5
6
# Start node with an environment variable
someVar=value node 
>
> // access env var
> console.log(process.env.someVar);
value

If Node is running a script (i.e. not in REPL mode), you can access the special variables __dirname and __filename which denote the paths to the currently executing script/module and its directory. This comes handy when accessing resources from relative folders.

Compiling with Babel

The latest versions of Node support ES6 almost fully. However, some features (e.g. ES6-style import/export) are not yet supported. If we want to use them, we’ll have to compile our code to an earlier JavaScript version with Babel.

We need to install it as a dev dependency:

1
npm install --save-dev babel-cli

Babel uses presets, which define what code transformations will be applied. Let’s install a basic ES6 preset:

1
npm install babel-preset-env --save-dev

Babel loads the preset from its config file .babelrc. Hence, we should specify it there as:

1
2
3
{
  "presets": ["env"]
}

In the most typical use case, Babel would take a source folder with ES6 code and generate the compiled output in another folder. If all our code is in the src/ sub-folder, we can compile to the build/ folder as follows:

1
./node_modules/.bin/babel src/ -d build/

We can then run the newly generated code in build/ as normal Node code by using the node or nodemon commands. Babel can monitor for changes, and compile automatically with the --watch option:

1
./node_modules/.bin/babel --watch src/ -d build/

The babel-node command combines compilation and execution in one step. Given a file srct/test.js you can compile and run as:

1
./node_modules/.bin/babel-node src/test.js

So how do we run this with nodemon? Here it is:

1
nodemon --exec ./node_modules/.bin/babel-node src/test.js

It is convenient to define command aliases in package.json. Assuming a file src/test.js exists, we can use:

1
2
3
4
5
6
"scripts": {
  "package": "babel src/ -d build/",
  "runDev": "babel-node src/test.js",
  "debugDev": "babel-node debug src/test.js",
  "monitorDev": "nodemon --exec babel-node src/test.js"
}

And then we can invoke them with:

1
2
3
4
npm run package    # Compiles all the code
npm run runDev     # Compiles and runs
npm run debugDev   # Compiles and debugs
npm run monitorDev # Compiles, runs, and monitors

NOTE: We could have installed Babel globally, like we did with nodemon. In this case, we would have all babel utilities on the Path, and we would not have to use relative paths (i.e. ./node_modules/.bin). Both approaches are possible.

The Flow Typechecker

Many developers prefer statically typed languages like Scala, Java, and C++. While there are compilers from such languages to JavaScript (e.g. TypeScript, GWT, and Scala.js), incorporating type safety into existing JavaScript code bases is far from trivial.

Enter Flow! It is a static type checker for JavaScript. You can incrementally add Flow type annotations to your JavaScript code instead of rewriting it all in another language.

First, lets install Flow globally:

1
npm install flow-bin -g

This will add the flow command to your terminal environment. Then we need to initialise Flow within the node project directory:

1
flow init

This will create a .flowconfig file with Flow settings.

JavaScript syntax does not support type annotations and adding them would result in syntax errors. One workaround is to add the types as comments which Flow will interpret:

1
2
//@flow
const c /*: number*/ = 'Not a number';

Note the //@flow comment at the beginning of the file. It tells Flow to process the file – otherwise it will ignore it. To analyse the code for type violations, just run flow from the project’s folder. This should report all type errors.

Defining type information in comments is not very convenient. We can use Babel to remove the type annotations for us. Given the Babel setup from the previous secion, we also need to install a plugin for Flow:

1
npm install babel-plugin-transform-flow-strip-types --save-dev

And then the .babelrc file must be configured to use it:

1
2
3
4
{
  "presets": ["env"],
  "plugins": ["transform-flow-strip-types"]
}

Now we can use types in the code:

1
2
3
4
5
6
7
// @flow
function f(name: string): string {
    return `Hello ${name}`;
}

console.log(f('Bob'));    // Types match - all Good
console.log(f(5));        // Will generate Flow error

We can also enable Flow to type check how we use 3rd party libraries. There is a central repository of type definitions for many popular libraries. It can be accessed via the flow-typed module:

1
npm install flow-typed -g

This will enable Flow to dynamically pull library type definitions.

Finally, don’t forget to install a Flow plugin for your favorite editor/IDE to get type hints as you code.

Promises

Promises are available in JavaScript and are not specific to Node. However, they are quite important for many Node libraries for async operations, and thus we’ll review them quickly. A promise allows us to work succinctly with async operations without nested boilerplate callbacks.

A promise is an instance of class Promise. It is a wrapper of a higher order function which takes two callback functions as parameters called resolve and reject. The function calls them when the encapsulated operation succeeds or fails respectively. The following example shows a promise which succeeds if a random number is less than 0.5:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// A Promise wraps a higher order function.
// "resolve" and "reject" are callback functions.
function promiseFunction(resolve, reject) {
  const rn = Math.random();

  if(rn < 0.5) {
    // Signal for success
    resolve(rn);
  } else {
    // Signal for failure
    reject('Bad luck');
  }
};

// Wrap the function in a Promise
let promise = new Promise(promiseFunction);

We can “chain” actions after a promise with the then method, which is also a higher order function. It takes two parameters – a function which is called “on success” and one for failure. Alternatively, we can provide only the “on success” function, and then chain a call to the catch method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// If called, the "number" parameter is the arguement of the 
// resolve function call from the promise
const successF = number => {
  console.log(`Resolve was called with ${number}`);
}

// If called, the "err" parameter is the arguement of 
// the reject function call from the promise
const errF = err => {
  console.log(`Reject was called with "${err}"`);
}

// The same as "promise.then(successF).catch(errF);"
promise.then(successF, errF);

Often, we need to run multiple async operations in a sequence. For example, we can call a web service, and if successfull make an SQL query, and if successfull … In other words, we need to chain promises. We can easily achieve this by returning a promise from our success-handling function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// A success handling function that reuturns a promise itself
const handleSuccessWithAnotherPromise = number => {
  return new Promise(promiseFunction);
}

// Prints a number if two consequtive invocations of
// "Math.random" returned < 0.5
promise.
  // Handle with a function returning a promise
  then(handleSuccessWithAnotherPromise). 

  // Now we can chain. "console.log" will be run
  // only if both promises have succeeded
  then(console.log).

  // Catch errors of any promise from the chain
  catch(console.log);
comments powered by Disqus