1. Background
One of the more difficult problems with writing systems that communicate over the network is managing input and output—that is, the reading and writing of data to and from the network and hard drive. Moving data around takes time, and scheduling it cleverly can make a big difference in how quickly a system responds to the user or to network requests.
In such programs, asynchronous programming is often helpful. It allows the program to send and receive data from and to multiple devices at the same time without complicated thread management and synchronization.
Node was initially conceived for the purpose of making asynchronous programming easy and convenient. JavaScript lends itself well to a system like Node. It is one of the few programming languages that does not have a built-in way to do input and output. Thus, JavaScript could be fit onto Node’s rather eccentric approach to in- and output without ending up with two inconsistent interfaces. In 2009, when Node was being designed, people were already doing callback-based programming in the browser, so the community around the language was used to an asynchronous programming style.
2. The node Command
When Node.js is installed on a system, it provides a program called node, which is used to run JavaScript files. Say you have a file hello.js, containing this code:
let message = “Hello world”;
console.log(message);
You can then run node from the command line like this to execute the program:
$ node hello.js
Hello world
The console.log method in Node does something similar to what it does in the browser. It prints out a piece of text. But in Node, the text will go to the process’s standard output stream, rather than to a browser’s JavaScript console. When running node from the command line, that means you see the logged values in your terminal.
If you run node without giving it a file, it provides you with a prompt at which you can type JavaScript code and immediately see the result.
$ node >
> 1 + 1
2
> [-1, -2, -3].map(Math.abs)
[1, 2, 3]
> process.exit(0)
$
The process binding, just like the console binding, is available globally in Node. It provides various ways to inspect and manipulate the current program. The exit method ends the process and can be given an exit status code, which tells the program that started node (in this case, the command line shell) whether the program completed successfully (code zero) or encountered an error (any other code).
To find the command line arguments given to your script, you can read process.argv, which is an array of strings. Note that it also includes the name of the node command and your script name, so the actual arguments start at index 2. If showargv.js contains the statement console.log(process.argv), you could run it like this:
$ node showargv.js one –and two
[“node”, “/tmp/showargv.js”, “one”, “–and”, “two”]
All the standard JavaScript global bindings, such as Array, Math, and JSON, are also present in Node’s environment. Browser-related functionality, such as document or prompt, is not.
3. Modules
Beyond the bindings I mentioned, such as console and process, Node puts few additional bindings in the global scope. If you want to access built-in functionality, you have to ask the module system for it.
The CommonJS module system, based on the require function, was described in “CommonJS” on page 171. This system is built into Node and is used to load anything from built-in modules to downloaded packages to files that are part of your own program.
When require is called, Node has to resolve the given string to an actual file that it can load. Pathnames that start with /, ./, or ../ are resolved relative to the current module’s path, where ./ stands for the current directory, ../ for one directory up, and / for the root of the file system. So if you ask for “./graph” from the file /tmp/robot/robot.js, Node will try to load the file /tmp/robot/graph.js.
The .js extension may be omitted, and Node will add it if such a file exists. If the required path refers to a directory, Node will try to load the file named index.js in that directory.
When a string that does not look like a relative or absolute path is given to require, it is assumed to refer to either a built-in module or a module installed in a node_modules directory. For example, require(“fs”) will give you Node’s built-in file system module. And require(“robot”) might try to load the library found in node_modules/robot/. A common way to install such libraries is by using NPM, which we’ll come back to in a moment.
Let’s set up a small project consisting of two files. The first one, called main.js, defines a script that can be called from the command line to reverse a string.
const {reverse} = require(“./reverse”);
// Index 2 holds the first actual command line argument
let argument = process.argv[2];
console.log(reverse(argument));
The file reverse.js defines a library for reversing strings, which can be used both by this command line tool and by other scripts that need direct access to a string-reversing function.
exports.reverse = function(string) {
return Array.from(string).reverse().join(“”);
};
Remember that adding properties to exports adds them to the interface of the module. Since Node.js treats files as CommonJS modules, main.js can take the exported reverse function from reverse.js.
We can now call our tool like this:
$ node main.js JavaScript
tpircSavaJ
Source: Haverbeke Marijn (2018), Eloquent JavaScript: A Modern Introduction to Programming, No Starch Press; 3rd edition.