{{meta {code_links: "[\"code/file_server.js\"]"}}} # Node.js {{quote {author: "Master Yuan-Ma", title: "The Book of Programming", chapter: true} A student asked 'The programmers of old used only simple machines and no programming languages, yet they made beautiful programs. Why do we use complicated machines and programming languages?'. Fu-Tzu replied 'The builders of old used only sticks and clay, yet they made beautiful huts.' quote}} {{index "command line", "Yuan-Ma", "Book of Programming"}} So far, we have used the JavaScript language in a single environment: the browser. This chapter and the [next one](skillsharing) will briefly introduce ((Node.js)), a program that allows you to apply your JavaScript skills outside of the browser. With it, you can build anything from small command-line tools to HTTP ((server))s that power dynamic ((website))s. These chapters aim to teach you the main concepts that Node.js uses and to give you enough information to write useful programs for it. They do not try to be a complete, or even a thorough, treatment of the platform. {{if interactive Whereas you could run the code in previous chapters directly on these pages, because it was either raw JavaScript or written for the browser, the code samples in this chapter are written for Node and often won't run in the browser. if}} If you want to follow along and run the code in this chapter, you'll need to install Node.js version 10 or higher. To do so, go to [_nodejs.org_](https://nodejs.org) and follow the installation instructions for your operating system. You can also find further ((documentation)) for Node.js there. ## Background {{index responsiveness, input}} 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. {{index "programming language", "Node.js", standard}} 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 in- 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. ## The node command {{index "node program"}} 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: ```{lang: null} $ node hello.js Hello world ``` {{index "console.log"}} 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' ((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)). {{index "node program", "read-eval-print loop"}} 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. ```{lang: null} $ node > 1 + 1 2 > [-1, -2, -3].map(Math.abs) [1, 2, 3] > process.exit(0) $ ``` {{index "process object", "global scope", [binding, global], "exit method", "status code"}} 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). {{index "command line", "argv property"}} 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: ```{lang: null} $ node showargv.js one --and two ["node", "/tmp/showargv.js", "one", "--and", "two"] ``` {{index [binding, global]}} 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. ## Modules {{index "Node.js", "global scope", "module loader"}} Beyond the few bindings I mentioned, such as `console` and `process`, Node puts few bindings in the global scope. If you want to access built-in functionality, you have to ask the module system for it. {{index "require function"}} The ((CommonJS)) module system, based on the `require` function, was described in [Chapter ?](modules#commonjs). This system is built into Node and is used to load anything from built-in ((module))s to downloaded ((package))s to ((file))s that are part of your own program. {{index [path, "file system"], "relative path", resolution}} 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`. {{index "index.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. {{index "node_modules directory", 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. {{index "require function", "Node.js", "garble example"}} Let's set up a small project consisting of two files. The first one is called `main.js`, and 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)); ``` {{index reuse, "Array.from function", "join method"}} 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(""); }; ``` {{index "exports object", CommonJS}} Remember that adding properties to `exports` adds them to the ((interface)) of the module. Since Node.js treats files as ((CommonJS)) ((module))s, `main.js` can take the exported `reverse` function from `reverse.js`. We can now call our tool like this: ```{lang: null} $ node main.js JavaScript tpircSavaJ ``` ## Installing with NPM {{index NPM, "Node.js", "npm program", library}} NPM, which was introduced in [Chapter ?](modules#modules_npm), is an online repository of JavaScript ((module))s, many of which are specifically written for Node. When you install Node on your computer, you also get the `npm`, which you can use to interact with this repository. {{index "ini package"}} Its main use is ((download))ing packages. We saw the `ini` package in [Chapter ?](modules#modules_ini). We can use NPM to fetch and install that package on our computer. ```{lang: null} $ npm install ini npm WARN enoent ENOENT: no such file or directory, open '/tmp/package.json' + ini@1.3.5 added 1 package in 0.552s $ node > const {parse} = require("ini"); > parse("x = 1\ny = 2"); { x: '1', y: '2' } ``` {{index "require function", "node_modules directory", "npm program"}} After running `npm install`, ((NPM)) will have created a directory called `node_modules`. Inside that directory will be an `ini` directory which contains the ((library)). You can open it and look at the code. When we call `require("ini")`, this library is loaded, and we can call its `parse` property to parse a configuration file. By default NPM installs packages under the current directory, rather than in a central place. If you are used to other package managers, this may seem unusual, but it has advantages—it puts each application in full control of the packages it installs, and makes it easier to manage versions and clean up when removing an application. ### Package files {{index "package.json", dependency}} In the `npm install` example, you could see a ((warning)) about the fact that the `package.json` file did not exist. It is recommended to create such a file for each project, either manually or by running `npm init`. It contains some information about the project, such as its name and ((version)), and lists its dependencies. The robot simulation from [Chapter ?](robot), as modularized in [Exercise 10.1](modules#modular_robot), might have a `package.json` file like this: ```{lang: "application/json"} { "author": "Marijn Haverbeke", "name": "eloquent-javascript-robot", "description": "Simulation of a package-delivery robot", "version": "1.0.0", "main": "run.js", "dependencies": { "dijkstrajs": "^1.0.1", "random-item": "^1.0.0" }, "license": "ISC" } ``` {{index "npm program", tool}} When you run `npm install` without naming a package to install, NPM will install the dependencies listed in `package.json`. When you install a specific package that is not already listed as a dependency, NPM will add it to `package.json`. ### Versions {{index "package.json", dependency, evolution}} A `package.json` file lists both the program's own ((version)) and versions for its dependencies. Versions are a way to deal with the fact that ((package))s evolve separately, and code written to work with a package as it existed at one point may not work with a later, modified version of the package. {{index compatibility}} NPM demands that its packages follow a schema called _((semantic versioning))_, which encodes some information about which versions are _compatible_ (don't break the old interface) in the version number. A semantic version consists of three numbers, separated by periods, such as `2.3.0`. Every time new functionality is added, the middle number has to be incremented. Every time compatibility is broken, so that existing code that uses the package might not work with the new version, the first number has to be incremented. {{index "caret character"}} A caret character (`^`) in front of the version number for a dependency in `package.json` indicates that any version compatible with the given number may be installed. So for example `"^2.3.0"` would mean that any version greater than or equal to 2.3.0 and less than 3.0.0 is allowed. {{index publishing}} The `npm` command is also used to publish new packages or new versions of packages. If you `npm publish` in a ((directory)) that has a `package.json` file, it will publish a package with the name and version listed in the JSON file to the registry. Anyone can publish packages to NPM—though only under a new name, since it would be somewhat scary if random people could update existing packages. Since the `npm` program is a piece of software that talks to an open system—the package registry—there is nothing unique about what it does. Another program, `yarn`, which can be installed from the NPM registry, fills the same role as `npm` using a somewhat different interface and installation strategy. This book won't delve further into the details of ((NPM)) usage. Refer to [_npmjs.org_](https://npmjs.org) for further documentation and a way to search for packages. ## The file system module {{index directory, "fs package", "Node.js"}} One of the most commonly used built-in modules in Node is the `fs` module, which stands for _((file system))_. It exports functions for working with ((file))s and directories. {{index "readFile function", "callback function"}} For example, there is a function called `readFile` which reads a file and then calls a callback with the file's contents. ``` let {readFile} = require("fs"); readFile("file.txt", "utf8", (error, text) => { if (error) throw error; console.log("The file contains:", text); }); ``` {{index "Buffer class"}} The second argument to `readFile` indicates the _((character encoding))_ used to decode the file into a string. There are several ways in which ((text)) can be encoded to ((binary data)), but most modern systems use ((UTF-8)). So unless you have reasons to believe another encoding is used, pass `"utf8"` when reading a text file. If you do not pass an encoding, Node will assume you are interested in the binary data and will give you a `Buffer` object instead of a string. This is an ((array-like object)) that contains numbers representing the bytes (8-bit chunks of data) in the files. ``` const {readFile} = require("fs"); readFile("file.txt", (error, buffer) => { if (error) throw error; console.log("The file contained", buffer.length, "bytes.", "The first byte is:", buffer[0]); }); ``` {{index "writeFile function", "file system"}} A similar function, `writeFile`, is used to write a ((file)) to disk. ``` const {writeFile} = require("fs"); writeFile("graffiti.txt", "Node was here", err => { if (err) console.log(`Failed to write file: ${err}`); else console.log("File written."); }); ``` {{index "Buffer class", "character encoding"}} Here it was not necessary to specify the encoding—`writeFile` will assume that when it is given a string to write, rather than a `Buffer` object, it should write it out as text using its default character encoding, which is ((UTF-8)). {{index "fs package", "readdir function", "stat function", "rename function", "unlink function"}} The `fs` module contains many other useful functions: `readdir` will return the ((file))s in a ((directory)) as an array of strings, `stat` will retrieve information about a file, `rename` will rename a file, `unlink` will remove one, and so on. See the documentation at [_nodejs.org_](https://nodejs.org) for specifics. {{index "asynchronous programming", "Node.js", "error handling", "callback function"}} And most of these take a callback function as last parameter, which they call either with an error (the first argument), or a successful result (the second). As we saw in [Chapter ?](async), there are downsides to this style of programming—the biggest one being that error handling becomes verbose and error-prone. {{index "Promise class", "fs/promises package"}} Though promises have been part of JavaScript for a while, at the time of writing their integration into Node.js is still a work in progress. There is a package called `fs/promises` in the standard library since version 10, which exports most of the same functions as `fs`, but using promises rather than callback functions. ``` const {readFile} = require("fs/promises"); readFile("file.txt", "utf8") .then(text => console.log("The file contains:", text)); ``` {{index "synchronous programming", "fs package", "readFileSync function"}} Sometimes you don't need asynchronicity, and it just gets in the way. Many of the functions in `fs` also have a synchronous variant, which has the same name with `Sync` added to the end. For example, the synchronous version of `readFile` is called `readFileSync`. ``` const {readFileSync} = require("fs"); console.log("The file contains:", readFileSync("file.txt", "utf8")); ``` {{index optimization, performance, blocking}} Do note that while such a synchronous operation is being performed, your program is stopped entirely. If it should be responding to the user or to other machines on the network, being stuck on a synchronous action might produce annoying delays. ## The HTTP module {{index "Node.js", "http package"}} Another central module is called `http`. It provides functionality for running ((HTTP)) ((server))s and making HTTP ((request))s. {{index "listening (TCP)", "listen method", "createServer function"}} This is all it takes to start an HTTP server: ``` const {createServer} = require("http"); let server = createServer((request, response) => { response.writeHead(200, {"Content-Type": "text/html"}); response.write(`
You asked for ${request.url}