Running and testing Node.js commands

Now that you’ve installed Node.js, we want to do two things—verify that the installation was successful and familiarize ourselves with the Node.js command-line tools and running simple scripts with Node.js. We’ll also touch again on async functions and look at a simple example HTTP server. We’ll finish off with the npm and npx command-line tools.

1. Using Node.js’s command-line tools

The basic installation of Node.js includes two commands: node and npm. We’ve already seen the node command in action. It’s used either for running command-line scripts or server processes. The other, npm, is a package manager for Node.js.

The easiest way to verify that your Node.js installation works is also the best way to get help with Node.js. Type the following command:

$ node –help

Usage: node [options] [ -e script | script.js | – ] [arguments]

  node inspect script.js [arguments]

 Options:

-v, –version print Node.js version

-e, –eval script evaluate script

-p, –print evaluate script and print result

-c, –check syntax check script without executing

-i, –interactive always enter the REPL even if stdin

does not appear to be a terminal

-r, –require module to preload (option can be repeated)

– script read from stdin (default; interactive mode if a tty)

… many more options Environment variables:

NODE_DEBUG ‘,’-separated list of core modules that should print debug information

NODE_DEBUG_NATIVE ‘,’-separated list of C++ core debug categories that should print debug output

NODE_DISABLE_COLORS set to 1 to disable colors in the REPL NODE_EXTRA_CA_CERTS path to additional CA certificates file NODE_NO_WARNINGS set to 1 to silence process warnings

NODE_OPTIONS set CLI options in the environment via a space-separated list

NODE_PATH ‘:’-separated list of directories prefixed to the module search path

… many more environment variables 

That was a lot of output but don’t study it too closely. The key takeaway is that node–help provides a lot of useful information.

Note that there are options for both Node.js and V8 (not shown in the previous command line). Remember that Node.js is built on top of V8; it has its own universe of options that largely focus on details of bytecode compilation or garbage collection and heap algorithms. Enter node –v8-options to see the full list of these options.

On the command line, you can specify options, a single script file, and a list of arguments to that script. We’ll discuss script arguments further in the following section, Running a simple script with Node.js.

Running Node.js with no arguments drops you in an interactive JavaScript shell:

$ node

> log(‘Hello, world!’);

Hello, world!

undefined

Any code you can write in a Node.js script can be written here. The command interpreter gives a good terminal-oriented user experience and is useful for interactively playing with your code. You do play with your code, don’t you? Good!

2. Running a simple script with Node.js

Now, let’s look at how to run scripts with Node.js. It’s quite simple; let’s start by referring to the help message shown previously. The command-line pattern is just a script filename and some script arguments, which should be familiar to anyone who has written scripts in other languages.

For this and other examples in this book, it doesn’t truly matter where you put the files. However, for the sake of neatness, you can start by making a directory named node-web-dev in the home directory of your computer and inside that, creating one directory per chapter (for example, chap02 and chap03).

First, create a text file named ls.js with the following content:

const fs = require(‘fs’).promises;

async function listFiles() {

try {

const files = await fs.readdir(‘.’);

for (const file of files) {

console.log(file);

}

} catch (err) {

console.error(err);

}

}

listFiles();

Next, run it by typing the following command:

$ node ls.js

ls.js 

This is a pale and cheap imitation of the Unix ls command (as if you couldn’t figure that out from the name!). The readdir function is a close analog to the Unix readdir system call used to list the files in a directory. On Unix/Linux systems, we can run the following command to learn more:

$ man 3 readdir

 The man command, of course, lets you read manual pages and section 3 covers the C library.

Inside the function body, we read the directory and print its contents. Using require(‘fs’).promises gives us a version of the fs module (filesystem functions) that returns Promises; it, therefore, works well in an async function. Likewise, the ES2015 for..of loop construct lets us loop over entries in an array in a way that works well in async functions.

This script is hardcoded to list files in the current directory. The real ls command takes a directory name, so let’s modify the script a little.

Command-line arguments land in a global array named process.argv. Therefore, we can modify ls.js, copying it as ls2.js (as follows) to see how this array works:

const fs = require(‘fs’).promises;

async function listFiles() {

try {

var dir = ‘.’;

if (process.argv[2]) dir = process.argv[2];

const files = await fs.readdir(dir);

for (let fn of files) {

console.log(fn);

}

} catch (err) {

console.error(err);

}

}

listFiles();

You can run it as follows:

$ pwd

/Users/David/chap02

$ node ls2 ..

chap01

chap02

$ node ls2

app.js

ls.js

ls2.js 

We simply checked whether a command-line argument was present, if (process.argv[2]). If it was, we override the value of the dir variable, dir = process.argv[2], and we then use that as the readdir argument:

$ node ls2.js /nonexistent

{ Error: ENOENT: no such file or directory, scandir ‘/nonexistent’

errno: -2,

code: ‘ENOENT’,

syscall: ‘scandir’,

path: ‘/nonexistent’ }

If you give it a non-existent directory pathname, an error will be thrown and printed using the catch clause.

2.1. Writing inline async arrow functions

There is a different way to write these examples that some feel is more concise. These examples were written as a regular function—with the function keyword—but with the async keyword in front. One of the features that came with ES2015 is the arrow function, which lets us streamline the code a little bit.

Combined with the async keyword, an async arrow function looks like this:

async () => {

// function body

}

You can use this anywhere; for example, the function can be assigned to a variable or it can be passed as a callback to another function. When used with the async keyword, the body of the arrow function has all of the async function’s behavior.

For the purpose of these examples, an async arrow function can be wrapped for immediate execution:

(async () => {

// function body

})()

The final parenthesis causes the inline function to immediately be invoked.

Then, because async functions return a Promise, it is necessary to add a .catch block to catch errors. With all that, the example looks as follows:

const fs = require(‘fs’);

(async () => {

var dir = ‘.’;

if (process.argv[2]) dir = process.argv[2];

const files = await fs.readdir(dir);

for (let fn of files) {

console.log(fn);

}

})().catch(err => { console.error(err); });

Whether this or the previous style is preferable is perhaps a matter of taste. However, you will find both styles in use and it is necessary to understand how both work.

When invoking an async function at the top level of a script, it is necessary to capture any errors and report them. Failure to catch and report errors can lead to mysterious problems that are hard to pin down. For the original version of this example, the errors were explicitly caught with a try/catch block. In this version, we catch errors using a .catch block.

Before we had async functions, we had the Promise object and before that, we had the callback paradigm. All three paradigms are still used in Node.js, meaning you’ll need to understand each.

3. Converting to async functions and the Promise paradigm

In the previous section, we discussed util.promisify and its ability to convert a callback-oriented function into one that returns a Promise. The latter plays well with async functions and therefore, it is preferable for functions to return a Promise.

To be more precise, util.promisify is to be given a function that uses the error- first-callback paradigm. The last argument of such functions is a callback function, whose first argument is interpreted as an error indicator, hence the phrase error-first- callback. What util.promisify returns is another function that returns a Promise.

The Promise serves the same purpose as error-first-callback. If an error is indicated, the Promise resolves to the rejected status, while if success is indicated, the Promise resolves to a success status. As we see in these examples, the Promise is handled very nicely within an async function.

The Node.js ecosystem has a large body of functions that use error-first-callback. The community has began a conversion process where functions will return a Promise and possibly also take an error-first-callback for API compatibility.

One of the new features in Node.js 10 is an example of such a conversion. Within the fs module is a submodule, named fs.promises, with the same API but producing Promise objects. We wrote the previous examples using that API.

Another choice is a third-party module, fs-extra. This module has an extended API beyond the standard fs module. On one hand, its functions return a Promise if no callback function is provided or else invokes the callback. In addition, it includes several useful functions.

The util module has another function, util.callbackify, which does as the name implies—it converts a function that returns a Promise into one that uses a callback function.

Now that we’ve seen how to run a simple script, let’s look at a simple HTTP server.

4. Launching a server with Node.js

Many scripts that you’ll run are server processes; we’ll be running lots of these scripts later on. Since we’re still trying to verify the installation and get you familiar with using Node.js, we want to run a simple HTTP server. Let’s borrow the simple server script on the Node.js home page (http://nodejs.org).

Create a file named app.js, containing the following:

const http = require(‘http’); http.createServer(function (req, res) {

res.writeHead(200, {‘Content-Type’: ‘text/plain’});

res.end(‘Hello, World!\n’);

}).listen(8124, ‘127.0.0.1’);

console.log(‘Server running at http://127.0.0.1:8124’);

Run it as follows:

$ node app.js

Server running at http://127.0.0.1:8124 

This is the simplest of web servers you can build with Node.js. If you’re interested in how it works, flip forward to Chapter 4, HTTP Servers and Clients, Chapter 5, Your First Express Application, and Chapter 6, Implementing the Mobile-First Paradigm. But for now, just type http://127.0.0.1:8124 in your browser to see the Hello, World! message:

A question to ponder is why this script didn’t exit when ls.js did. In both cases, execution of the script reaches the end of the file; the Node.js process does not exit in app.js, while it does in ls.js.

The reason for this is the presence of active event listeners. Node.js always starts up an event loop and in app.js, the listen function creates an event, listener, that implements the HTTP protocol. This listener event keeps app.js running until you do something, such as press Ctrl + C in the terminal window. In ls.js, there is nothing there to create a long-running listener event, so when ls.js reaches the end of its script, the node process will exit.

To carry out more complex tasks with Node.js, we must use third-party modules. The npm repository is the place to go.

5. Using npm, the Node.js package manager

Node.js, being a JavaScript interpreter with a few interesting asynchronous I/O libraries, is by itself a pretty basic system. One of the things that makes Node.js interesting is the rapidly growing ecosystem of third-party modules for Node.js.

At the center of that ecosystem is the npm module repository. While Node.js modules can be downloaded as source and assembled manually for use with Node.js programs, that’s tedious to do and it’s difficult to implement a repeatable build process. npm gives us a simpler method; npm is the de facto standard package manager for Node.js and it greatly simplifies downloading and using these modules. We will talk about npm at length in the next chapter.

The sharp-eyed among you will have noticed that npm is already installed via all the installation methods discussed previously. In the past, npm was installed separately, but today it is bundled with Node.js.

Now that we have npm installed, let’s take it for a quick spin. The hexy program is a utility used for printing hex dumps of files. That’s a very 1970s thing to do, but it is still extremely useful. It serves our purpose right now as it gives us something to quickly install and try out:

$ npm install -g hexy

/opt/local/bin/hexy ->

/opt/local/lib/node_modules/hexy/bin/hexy_cmd.js

+ hexy@0.2.10

added 1 package in 1.107s 

Adding the -g flag makes the module available globally, irrespective of the present working directory of your command shell. A global install is most useful when the module provides a command-line interface. When a package provides a command- line script, npm sets that up. For a global install, the command is installed correctly for use by all users of the computer.

Depending on how Node.js is installed for you, it may need to be run with sudo:

$ sudo npm install -g hexy

Once it is installed, you’ll be able to run the newly–installed program this way:

$ hexy –width 12 ls.js

$ hexy –width 12 ls.js
00000000: 636f 6e73 7420 6673 203d 2072 const.fs.=.r
0000000c: 6571 7569 7265 2827 6673 2729 equire(‘fs’)
00000018: 3b0a 636f 6e73 7420 7574 696c ;.const.util
00000024: 203d 2072 6571 7569 7265 2827 .=.require(‘
00000030: 7574 696c 2729 3b0a 636f 6e73 util’);.cons
0000003c: 7420 6673 5f72 6561 6464 6972 t.fs_readdir
00000048: 203d 2075 7469 6c2e 7072 6f6d .=.util.prom
00000054: 6973 6966 7928 6673 2e72 6561 isify(fs.rea
00000060: 6464 6972 293b 0a0a 2861 7379 ddir);..(asy
0000006c: 6e63 2028 2920 3d3e 207b 0a20 nc.().=>.{..
00000078: 2063 6f6e 7374 2066 696c 6573 .const.files
00000084: 203d 2061 7761 6974 2066 735f .=.await.fs_
00000090: 7265 6164 6469 7228 272e 2729 readdir(‘.’)
0000009c: 3b0a 2020 666f 7220 2866 6e20 ;…for.(fn.
000000a8: 6f66 2066 696c 6573 2920 7b0a of.files).{.
000000b4: 2020 2020 636f 6e73 6f6c 652e ….console.
000000c0: 6c6f 6728 666e 293b 0a20 207d log(fn);…}
000000cc: 0a7d 2928 292e 6361 7463 6828 .})().catch(
000000d8: 6572 7220 3d3e 207b 2063 6f6e err.=>.{.con
000000e4: 736f 6c65 2e65 7272 6f72 2865 sole.error(e
000000f0: 7272 293b 207d 293b           rr);.});

The hexy command was installed as a global command, making it easy to run.

Again, we’ll be doing a deep dive into npm in the next chapter. The hexy utility is both a Node.js library and a script for printing out these old-style hex dumps.

For npm-packaged command-line tools, there is another, simpler way to use the tool.

6. Using npx to execute Node.js packaged binaries

Some packages in the npm repository are command-line tools, such as the hexy program we looked at earlier. Having to first install such a program before using it is a small hurdle. The sharp-eyed among you will have noticed that npx is installed alongside the node and npm commands when installing Node.js. This tool is meant to simplify running command-line tools from the npm repository by removing the need to first install the package.

The previous example could have been run this way:

$ npx hexy –width 12 ls.js

Under the covers, npx uses npm to download the package to a cache directory, unless the package is already installed in the current project directory. Because the package is then in a cache directory, it is only downloaded once.

There are a number of interesting options to this tool; to learn more, go to https://www.npmjs.com/package/npx.

We have learned a lot in this section about the command-line tools delivered with Node.js, as well as ran a simple script and HTTP server. Next, we will learn how advances in the JavaScript language affect the Node.js platform.

Source: Herron David (2020), Node.js Web Development: Server-side web development made easy with Node 14 using practical examples, Packt Publishing.

Leave a Reply

Your email address will not be published. Required fields are marked *