Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Node.js Cookbook
Node.js Cookbook

Node.js Cookbook : Practical recipes for building server-side web applications with Node.js 22 , Fifth Edition

Arrow left icon
Profile Icon Bethany Griggs Profile Icon Manuel Spigolon
Arrow right icon
€15.99 €23.99
eBook Nov 2024 456 pages 5th Edition
eBook
€15.99 €23.99
Paperback
€29.99
Subscription
Free Trial
Renews at €18.99p/m
Arrow left icon
Profile Icon Bethany Griggs Profile Icon Manuel Spigolon
Arrow right icon
€15.99 €23.99
eBook Nov 2024 456 pages 5th Edition
eBook
€15.99 €23.99
Paperback
€29.99
Subscription
Free Trial
Renews at €18.99p/m
eBook
€15.99 €23.99
Paperback
€29.99
Subscription
Free Trial
Renews at €18.99p/m

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
OR
Modal Close icon
Payment Processing...
tick Completed

Billing Address

Table of content icon View table of contents Preview book icon Preview Book

Node.js Cookbook

Interacting with the File System

Before Node.js, JavaScript was predominantly used in the browser. Node.js brought JavaScript to the server and enabled us to interact with the operating system through JavaScript. Today, Node.js is one of the most popular technologies for building server-side applications.

Node.js interacts with the operating system at a fundamental level: input and output (I/O). This chapter will explore the core APIs provided by Node.js that allow us to interact with standard I/O, the file system, and the network stack.

This chapter will show you how to read and write files both synchronously and asynchronously. Node.js was built to handle asynchronous code and enable a non-blocking model. Understanding how to read and write asynchronous code is fundamental learning, and it will show how to leverage the capabilities of Node.js.

We will also learn about the core modules provided by Node.js. We’ll be focusing on the File System module, which enables you to interact with the file system and files. Newer versions of Node.js have added Promise variants of many file system APIs, which will also be touched upon in this chapter.

This chapter will cover the following recipes:

  • Interacting with the file system
  • Working with files
  • Fetching metadata
  • Watching files

Technical requirements

This chapter assumes that you have a recent version of Node.js 22 installed, a Terminal or shell, and an editor of your choice. The code for this chapter is available on GitHub at https://github.com/PacktPublishing/Node.js-Cookbook-Fifth-Edition in the Chapter02 directory.

This chapter will use the CommonJS syntax; refer to Chapter 5 for more information on CommonJS and ECMAScript modules.

Interacting with the file system

Standard in (stdin) refers to an input stream that a program can use to read input from a command shell or Terminal. Similarly, standard out (stdout) refers to the stream that is used to write the output. Standard error (stderr) is a separate stream to stdout that is typically reserved for outputting errors and diagnostic data.

In this recipe, we’re going to learn how to handle input with stdin, write output to stdout, and log errors to stderr.

Getting ready

For this recipe, let’s first create a single file named greeting.js. The program will ask for user input via stdin, return a greeting via stdout, and log an error to stderr when invalid input is provided. Let’s create a directory to work in, too:

$ mkdir interfacing-with-io
$ cd interfacing-with-io
$ touch greeting.js

Now that we’ve set up our directory and file, we’re ready to move on to the recipe steps.

How to do it…

In this recipe, we’re going to create a program that can read from stdin and write to stdout and stderr:

  1. First, we need to tell the program to listen for user input. This can be done by adding the following lines to greeting.js:
    console.log('What is your name?');
    process.stdin.on('data', (data) => {
      // processing on each data event
    });
  2. We can run the file using the following command. Observe that the application does not exit because it is continuing to listen for process.stdin data events:
    $ node greeting.js
  3. Exit the program using Ctrl + C.
  4. We can now tell the program what it should do each time it detects a data event. Add the following lines below the // processing on each data event comment:
      const name = data.toString().trim().toUpperCase();
      process.stdout.write(`Hello ${name}!`);
  5. You can now type input to your program. When you press Enter, it will return a greeting and your name in uppercase:
    $ node greeting.js
    What is your name?
    Beth
    Hello BETH!
  6. We can now add a check for whether the input string is empty and log to stderr if it is. Change your file to the following:
    console.log('What is your name?');
    process.stdin.on('data', (data) => {
      // processing on each data event
      const name = data.toString().trim().toUpperCase();
      if (name !== '') {
        process.stdout.write(`Hello ${name}!`);
      } else {
        process.stderr.write('Input was empty.\n');
      }
    });
  7. Run the program again and hit Enter with no input:
    $ node greeting.js
    What is your name?
    Input was empty.

We’ve now created a program that can read from stdin and write to stdout and stderr.

How it works…

The process.stdin, process.stdout, and process.stderr properties are all properties on the process object. A global process object provides information and control of the Node.js process. For each of the I/O channels (standard in, standard out, standard error), they emit data events for every chunk of data received. In this recipe, we were running the program in interactive mode where each data chunk was determined by the newline character when you hit Enter in your shell.

The process.stdin.on('data', (data) => {...}); instance is what listens for these data events. Each data event returns a Buffer object. The Buffer object (typically named data) returns a binary representation of the input.

The const name = data.toString() instance is what turns the Buffer object into a string. The trim() function removes all whitespace characters – including spaces, tabs, and newline characters – from the beginning and end of a string. The whitespace characters include spaces, tabs, and newline characters.

We write to stdout and stderr using the respective properties on the process object (process.stdout.write, process.stderr.write).

During the recipe, we also used Ctrl + C to exit the program in the shell. Ctrl + C sends SIGINT, or signal interrupt, to the Node.js process. For more information about signal events, refer to the Node.js Process API documentation: https://nodejs.org/api/process.html#process_signal_events.

Important note

Console APIs: Under the hood, console.log and console.err are using process.stdout and process.stderr. Console methods are higher-level APIs and include automatic formatting. It’s typical to use console methods for convenience and lower-level process methods when you require more control over the stream.

There’s more…

As of Node.js 17.0.0, Node.js provides an Experimental Readline Promises API, which is used for reading a file line by line. The Promises API variant of this allows you to use async/await instead of callbacks, providing a more modern and cleaner approach to handling asynchronous operations.

Here is an example of how the Promises API variant can be used to create a similar program to the greeting.js file created in the main recipe:

const readline = require('node:readline/promises');
async function greet () {
  const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
      });
  const name = await rl.question('What is your name?\n');
  console.log(`Hello ${name}!`);
  rl.close();
}
greet();

This Node.js script utilizes the node:readline/promises module, which provides the Promise variant of the Readline API. It defines an asynchronous function, greet(), which prompts the user for their name in the console and then greets them with a personalized message – similar to the main recipe program. Using the Readline Promises API allows us to use the async/await syntax for cleaner asynchronous code flow. We’ll cover more about the async/await syntax in later recipes and chapters.

See also

Working with files

Node.js provides several core modules, including the fs module. fs stands for File System, and this module provides the APIs to interact with the file system.

In this recipe, and throughout the book, we will make use of the node: prefix when importing core modules.

In this recipe, we’ll learn how to read, write, and edit files using the synchronous functions available in the fs module.

Getting ready

Let’s start by preparing a directory and files for this recipe:

  1. Create another directory for this recipe:
    $ mkdir working-with-files
    $ cd working-with-files
  2. And now, let’s create a file to read. Run the following in your shell to create a file containing some simple text:
    $ echo Hello World! > hello.txt
  3. We’ll also need a file for our program—create a file named readWriteSync.js:
    $ touch readWriteSync.js

Important note

The touch utility is a command-line utility included in Unix-based operating systems that is used to update the access and modification date of a file or directory to the current time. However, when touch is run with no additional arguments on a non-existent file, it will create an empty file with that name. The touch utility is a typical way of creating an empty file.

How to do it…

In this recipe, we’ll synchronously read the file named hello.txt, manipulate the contents of the file, and then update the file using synchronous functions provided by the fs module:

  1. We’ll start by requiring the fs and path built-in modules. Add the following lines to readWriteSync.js:
    const fs = require('node:fs');
    const path = require('node:path');
  2. Now, let’s create a variable to store the file path of the hello.txt file that we created earlier:
    const filepath = path.join(process.cwd(), 'hello.txt');
  3. We can now synchronously read the file contents using the readFileSync() function provided by the fs module. We’ll also print the file contents to stdout using console.log():
    const contents = fs.readFileSync(filepath, 'utf8');
    console.log('File Contents:', contents);
  4. Now, we can edit the content of the file – we will convert the lowercase text into uppercase:
    const upperContents = contents.toUpperCase();
  5. To update the file, we can use the writeFileSync() function. We’ll also add a log statement afterward indicating that the file has been updated:
    fs.writeFileSync(filepath, upperContents);
    console.log('File updated.');
  6. Run your program with the following:
    $ node readWriteSync.js
    File Contents: Hello World!
    File updated.
  7. To verify the contents were updated, you can open or use cat in your Terminal to show the contents of hello.txt:
    $ cat hello.txt
    HELLO WORLD!

You now have a program that, when run, will read the contents of hello.txt, convert the text content into uppercase, and update the file.

How it works…

As is commonplace, the first two lines of the file require the necessary core modules for the program.

The const fs = require('node:fs'); line will import the core Node.js File System module. The API documentation for the Node.js File System module is available at https://nodejs.org/api/fs.html. The fs module provides APIs to interact with the file system using Node.js. Similarly, the core path module provides APIs for working with file and directory paths. The path module API documentation is available at https://nodejs.org/api/path.html.

Next, we defined a variable to store the file path of hello.txt using the path.join() and process.cwd() functions. The path.join() function joins the path sections provided as parameters with the separator for the specific platform (for example, / on Unix and \ on Windows environments).

The process.cwd() function is a function on the global process object that returns the current directory of the Node.js process. This program is expecting the hello.txt file to be in the same directory as the program.

Next, we read the file using the fs.readFileSync() function. We pass this function the file path to read and the encoding, UTF-8. The encoding parameter is optional—when the parameter is omitted, the function will default to returning a Buffer object.

To perform manipulation of the file contents, we used the toUpperCase() function available on string objects.

Finally, we updated the file using the fs.writeFileSync() function. We passed the fs.writeFileSync() function two parameters. The first parameter was the path to the file we wished to update, and the second parameter was the updated file contents.

Important note

Both the readFileSync() and writeFileSync() APIs are synchronous, which means that they will block/delay concurrent operations until the file read or write is completed. To avoid blocking, you’ll want to use the asynchronous versions of these functions, covered in the There’s more… section of the current recipe.

There’s more…

Throughout this recipe, we were operating on our files synchronously. However, Node.js was developed with a focus on enabling the non-blocking I/O model; therefore, in many (if not most) cases, you’ll want your operations to be asynchronous.

Today, there are three notable ways to handle asynchronous code in Node.js—callbacks, Promises, and async/await syntax. The earliest versions of Node.js only supported the callback pattern. Promises were added to the JavaScript specification with ECMAScript 2015, known as ES6, and subsequently, support for Promises was added to Node.js. Following the addition of Promise support, async/await syntax support was also added to Node.js.

All currently supported versions of Node.js now support callbacks, Promises, and async/await syntax – you may find any of these used in modern Node.js development. Let’s explore how we can work with files asynchronously using these techniques.

Working with files asynchronously

Asynchronous programming can enable some tasks or processing to continue while other operations are happening.

The program from the Working with files recipe was written using the synchronous functions available in the fs module:

const fs = require('node:fs');
const path = require('node:path');
const filepath = path.join(process.cwd(), 'hello.txt');
const contents = fs.readFileSync(filepath, 'utf8');
console.log('File Contents:', contents);
const upperContents = contents.toUpperCase();
fs.writeFileSync(filepath, upperContents);
console.log('File updated.');

This means that the program was blocked waiting for the readFileSync() and writeFileSync() operations to complete. This program can be rewritten to make use of asynchronous APIs.

The asynchronous version of readFileSync() is readFile(). The general convention is that synchronous APIs will have the term “sync” appended to their name. The asynchronous function requires a callback function to be passed to it. The callback function contains the code that we want to be executed when the asynchronous task completes.

The following steps will implement the same behavior as the program from the Working with files recipe but using asynchronous methods:

  1. The readFileSync() function in this recipe could be changed to use the asynchronous function with the following:
    const fs = require('node:fs');
    const path = require('node:path');
    const filepath = path.join(process.cwd(),
      'hello.txt');
    fs.readFile(filepath, 'utf8', (err, contents) => {
      if (err) {
        return console.log(err);
      }
      console.log('File Contents:', contents);
      const upperContents = contents.toUpperCase();
      fs.writeFileSync(filepath, upperContents);
      console.log('File updated.');
    });

    Observe that all the processing that is reliant on the file read needs to take place inside the callback function.

  2. The writeFileSync() function can also be replaced with the writeFile() asynchronous function:
    const fs = require('node:fs');
    const path = require('node:path');
    const filepath = path.join(process.cwd(),
      'hello.txt');
    fs.readFile(filepath, 'utf8', (err, contents) => {
      if (err) {
        return console.log(err);
      }
      console.log('File Contents:', contents);
      const upperContents = contents.toUpperCase();
      fs.writeFile(filepath, upperContents, (err) => {
        if (err) throw err;
        console.log('File updated.');
      });
    });

    Note that we now have an asynchronous function that calls another asynchronous function. It’s not recommended to have too many nested callbacks as it can negatively impact the readability of the code. Consider the following to see how having too many nested callbacks impedes the readability of the code, which is sometimes referred to as “callback hell”:

    first(args, () => {
        second(args, () => {
            third(args, () => {});
        });
    });
  3. Some approaches can be taken to avoid too many nested callbacks. One approach would be to split callbacks into explicitly named functions. For example, our file could be rewritten so that the writeFile() call is contained within its own named function, updateFile():
    const fs = require('node:fs');
    const path = require('node:path');
    const filepath = path.join(process.cwd(), 'hello.txt');
    fs.readFile(filepath, 'utf8', (err, contents) => {
      if (err) {
        return console.log(err);
      }
      console.log('File Contents:', contents);
      const upperContents = contents.toUpperCase();
      updateFile(filepath, upperContents);
    });
    function updateFile (filepath, contents) {
      fs.writeFile(filepath, contents, function (err) {
        if (err) throw err;
        console.log('File updated.');
      });
    }

    Another approach would be to use Promises, which we’ll cover in the Using the fs Promises API section of this chapter. But as the earliest versions of Node.js did not support Promises, the use of callbacks is still prevalent in many npm modules and existing applications.

  4. To demonstrate that this code is asynchronous, we can use the setInterval() function to print a string to the screen while the program is running. The setInterval() function enables you to schedule a function to happen after a specified delay in milliseconds. Add the following line to the end of your program:
    setInterval(() => process.stdout.write('**** \n'), 1).unref();

    Observe that the string continues to be printed every millisecond, even in between when the file is being read and rewritten. This shows that the file reading and writing have been implemented in a non-blocking manner because operations are still completing while the file is being handled.

Important note

Using unref() on setInterval() means this timer will not keep the Node.js event loop active. This means that if it is the only active event in the event loop, Node.js may exit. This is useful for timers for which you want to execute an action in the future but do not want to keep the Node.js process running solely.

  1. To demonstrate this further, you could add a delay between the reading and writing of the file. To do this, wrap the updateFile() function in a setTimeout() function. The setTimeout() function allows you to pass it a function and a delay in milliseconds:
    setTimeout(() => updateFile(filepath, upperContents), 10);
  2. Now, the output from our program should have more asterisks printed between the file read and write, as this is where we added the 10-millisecond delay:
    $ node readFileAsync.js
    ****
    ****
    File Contents: HELLO WORLD!
    ****
    ****
    ****
    ****
    ****
    ****
    ****
    ****
    ****
    File updated.

We can now see that we have converted the program from the Working with files recipe to handle the file operations asynchronously using the callback syntax.

Using the fs Promises API

The fs Promises API was released in Node.js v10.0.0. The API provides File System functions that return Promise objects rather than callbacks. Not all the original fs module APIs have equivalent Promise-based APIs, as only a subset of the original APIs were converted to provide Promise APIs. Refer to the Node.js API documentation for a full list of fs functions provided via the fs Promises API: https://nodejs.org/docs/latest/api/fs.html#promises-api.

A Promise is an object that is used to represent the completion of an asynchronous function. The naming is based on the general definition of the term “promise”—an agreement to do something or that something will happen. A Promise object is always in one of the three following states:

  • Pending
  • Fulfilled
  • Rejected

A Promise will initially be in the pending state and will remain pending until it becomes either fulfilled—when the task has completed successfully—or rejected—when the task has failed.

The following steps will implement the same behavior as the program from the recipe again but using fs Promises API methods:

  1. To use the API, you’ll first need to import it:
    const fs = require('node:fs/promises');
  2. It is then possible to read the file using the readFile() function:
    fs.readFile(filepath, 'utf8').then((contents) => {
        console.log('File Contents:', contents);
    });
  3. You can also combine the fs Promises API with the use of the async/await syntax:
    const fs = require('node:fs/promises');
    const path = require('node:path');
    const filepath = path.join(process.cwd(),
      'hello.txt');
    async function run () {
      try {
        const contents = await fs.readFile(filepath,
          'utf8');
        console.log('File Contents:', contents);
      } catch (error) {
        console.error(error);
      }
    }
    run();

Two notable aspects of this implementation are the use of the following:

  • async function run() {...}: Defines an asynchronous function named run(). Asynchronous functions enable the use of the await keyword for handling promises in a more synchronous-looking manner.
  • await fs.readFile(filepath, 'utf8'): Uses the await keyword to asynchronously read the contents of the file specified.

Now, we’ve learned how we can interact with files using the fs Promises API.

Important note

Owing to using CommonJS in this chapter, it was necessary to wrap the async/await example in a function as await must only be called from within an asynchronous function with CommonJS. From Chapter 5 onward, we’ll cover ECMAScript modules, where this wrapper function would be unnecessary due to top-level await being supported with ECMAScript modules.

See also

  • The Fetching metadata recipe in this chapter
  • The Watching files recipe in this chapter
  • Chapter 5

Fetching metadata

The fs module generally provides APIs that are modeled around Portable Operating System Interface (POSIX) functions. The fs module includes APIs that facilitate the reading of directories and file metadata.

In this recipe, we will create a small program that returns information about a file, using functions provided by the fs module.

Getting ready

  1. Get started by creating a directory to work in:
    $ mkdir fetching-metadata
    $ cd fetching-metadata
  2. We’ll also need to create a file to read and a file for our program:
    $ touch metadata.js
    $ touch file.txt

How to do it…

Using the files created in the Getting ready section, we will create a program that gives information about the file we pass to it as a parameter:

  1. As in the previous recipes, we first need to import the necessary core modules. For this recipe, we just need to import the fs module:
    const fs = require('node:fs');
  2. Next, we need the program to be able to read the filename as a command-line argument. To read the file argument, we can use process.argv[2]. Add the following line to your program:
    const file = process.argv[2];
  3. Now, we will create our printMetadata function:
    function printMetadata(file) {
      const fileStats = fs.statSync(file);
      console.log(fileStats);
    }
  4. Add a call to the printMetadata function:
    printMetadata(file);
  5. You can now run the program, passing it the./file.txt argument. Run your program with the following:
    $ node metadata.js ./file.txt
  6. Expect to see output like the following:
    Stats {
      dev: 16777231,
      mode: 33188,
      nlink: 1,
      uid: 501,
      gid: 20,
      rdev: 0,
      blksize: 4096,
      ino: 16402722,
      size: 0,
      blocks: 0,
      atimeMs: 1697208041116.9521,
      mtimeMs: 1697208041116.9521,
      ctimeMs: 1697208041116.9521,
      birthtimeMs: 1697208041116.9521,
      atime: 2023-10-13T14:40:41.117Z,
      mtime: 2023-10-13T14:40:41.117Z,
      ctime: 2023-10-13T14:40:41.117Z,
      birthtime: 2023-10-13T14:40:41.117Z
    }

    You can try adding some random text to file.txt, saving the file, and then rerunning your program; observe that the size and mtime values have been updated.

  7. Now, let’s see what happens when we pass a non-existent file to the program:
    $ node metadata.js ./not-a-file.txt
    node:fs:1658
      const stats = binding.stat(
                            ^
    Error: ENOENT: no such file or directory, stat './not-a-file.txt'

    The program throws an exception.

  8. We should catch this exception and output a message to the user saying the file path provided does not exist. To do this, change the printMetadata function to this:
    function printMetadata(file) {
      try {
        const fileStats = fs.statSync(file);
        console.log(fileStats);
      } catch (err) {
        console.error('Error reading file path:', file);
      }
    }
  9. Run the program again with a non-existent file:
    $ node metadata.js ./not-a-file.txt
    Error reading file: ./not-a-file.txt

This time, you should see that the program handled the error rather than throwing an exception.

How it works…

The process.argv property is a property on the global process object that returns an array containing the arguments that were passed to the Node.js process. The first element of the process.argv array, process.argv[0], is the path of the node binary that is running. The second element is the path of the file we’re executing – in this case, metadata.js. In the recipe, we passed the filename as the third command-line argument and, therefore, referenced it with process.argv[2].

Next, we created a printMetadata() function that called statSync(file). The statSync() function is a synchronous function that returns information about the file path that is passed to it. The file path passed can be either a file or a directory. The information returned is in the form of a stats object. The following table lists the information returned on the stats object:

Table 2.1 – Table listing properties returned on the stats object

Table 2.1 – Table listing properties returned on the stats object

Important note

In this recipe, we used only the synchronous File System APIs. For most of the fs APIs, there are both synchronous and asynchronous versions of each function. Refer to the Working with files asynchronously section of the Working with files recipe for more information about using asynchronous File System APIs.

In the final steps of this recipe, we edited our printMetadata function to account for invalid file paths. We did this by wrapping the statSync function in a try/catch statement.

There’s more…

Next, we’ll look at how we can check file access and modify file permissions and how to examine a symbolic link (symlink).

Checking file access

It is recommended that if you’re attempting to read, write, or edit a file, you follow the approach of handling the error if the file is not found, as we did in the recipe.

However, if you simply wanted to check the existence of a file, you could use the fs.access() or fs.accessSync() APIs. Specifically, the fs.access() function tests the user’s permissions for accessing the file or directory passed to it. The function also allows an optional argument of mode to be passed to it, where you can request the function to do a specific access check using Node.js file access constants. A list of Node.js file access constants is available in the Node.js fs module API documentation: https://nodejs.org/api/fs.html#fs_file_access_constants. These enable you to check whether the Node.js process can read, write, or execute the file path provided.

Important note

There is a legacy API that is now deprecated, called fs.exists(). It is not recommended you use this function. The reason for deprecation was that the method’s interface was found to be error-prone and could lead to accidental race conditions. The fs.access() or fs.stat() APIs should be used instead.

Modifying file permissions

The Node.js fs module provides APIs that can be used to alter the permissions on a given file. As with many of the other fs functions, there is both an asynchronous API, chmod(), and an equivalent synchronous API, chmodSync(). Both functions take a file path and mode as the first and second arguments, respectively. The chmod() function accepts a third parameter, which is the callback function to be executed upon completion.

Important note

The chmod command is used to change access permissions of file system objects on Unix and similar operating systems. If you’re unfamiliar with Unix file permissions, it is recommended you refer to the Unix manual pages (https://linux.die.net/man/1/chmod).

The mode argument can be either in the form of a numeric bitmask using a series of constants provided by the fs module or a sequence of three octal digits. The constants that can be used to create a bitmask to define user permissions are defined in the Node.js API documentation: https://nodejs.org/api/fs.html#fs_file_modes.

Imagine that you have a file that currently has the following permissions:

  • Owner readable and writeable
  • Group readable
  • Readable only by all other users (sometimes referred to as world readable)

If we wanted to additionally grant write access to those in the same group, we could use the following Node.js code:

const fs = require('node:fs');
const file = './file.txt';
fs.chmodSync(
  file,
  fs.constants.S_IRUSR |
    fs.constants.S_IWUSR |
    fs.constants.S_IRGRP |
    fs.constants.S_IWGRP |
    fs.constants.S_IROTH
);

As you can see, this code is quite verbose. Adding a complex series or permissions would require passing many constants to create a numeric bitmask. Alternatively, we can pass the chmodSync() function an octal representation of file permissions, as is commonplace when using the Unix chmod command on the command line.

We’re going to change the permissions using the equivalent of chmod 664 from the command line, but via Node.js:

const fs = require('fs');
const file = './file.txt';
fs.chmodSync(file, 0o664);

Important note

Refer to https://mason.gmu.edu/~montecin/UNIXpermiss.htm for more detailed information on how Unix permissions work.

Windows file permissions: The Windows operating system does not have as refined file permissions as on Unix—it is only possible to denote a file as writeable or non-writeable.

Inspecting symbolic links

A symlink is a special file that stores a reference to another file or directory. When the stat() or statSync() function from the Fetching metadata recipe is run on a symbolic link, the method will return information about the file the symbolic link is referencing rather than the symbolic link itself.

The Node.js fs module does, however, provide methods named lstat() and lstatSync() that inspect the symbolic link itself. The following steps will demonstrate how you can use these methods to inspect a symbolic link that we will create:

  1. To create a symbolic link, you can use the following command:
    $ ln -s file.txt link-to-file

    Now, you can use the Node.js Read-Eval-Print Loop (REPL) to test the lstatSync() function. The Node.js REPL is an interactive shell we can pass statements to, and it will evaluate them and return the result to the user.

  2. To enter the Node.js REPL, type node in your shell:
    $ node
    Welcome to Node.js v22.9.0.
    Type ".help" for more information.
    >
  3. You can then type commands such as the following:
    > console.log('Hello World!');
    Hello World!
    undefined
  4. Now, you can try out the lstatSync command:
    > fs.lstatSync('link-to-file');
    Stats {
      dev: 16777224,
      mode: 41453,
      nlink: 1,
      ...
    }

Note that we did not need to explicitly import the Node.js fs module. The REPL automatically loads the core (built-in) Node.js modules so that they are available to be used. The REPL is a useful tool for testing out commands without having to create files.

See also

  • The Watching files recipe in this chapter

Watching files

Node.js’s fs module provides functionality that enables you to watch files and track when files or directories are created, updated, or deleted.

In this recipe, we’ll create a small program named watch.js that watches for changes in a file using the watchFile() API and then prints a message when a change has occurred.

Getting ready

  1. For this recipe, we’ll want to work inside a new directory. Create and change into a directory called file-watching:
    $ mkdir file-watching
    $ cd file-watching
  2. We need to also create a file that we can watch:
    $ echo Hello World! > file.txt
  3. Create a watch.js file:
    $ touch watch.js

Now that we have created our directory and file, we can move on to the recipe.

How to do it…

We’re going to create a program that watches for changes in a given file – in this case, the file.txt file we created earlier. We will be using the fs module and, specifically, the watchFile() method to achieve this:

  1. To get started, import the required core Node.js modules:
    const fs = require('node:fs');
    const path = require('node:path');
  2. We also need the program to access a file we created:
    const file = path.join(process.cwd(), 'file.txt');
  3. Next, we call the fs.watchFile() function:
    fs.watchFile(file, (current, previous) => {
        return console.log(`${file} updated
          ${(current.mtime)}`);
    });
  4. Now, you can run the program in your shell with the following command:
    $ node watch.js
  5. In your editor, open file.txt and make some edits, saving between each one. You will notice that each time you save, a log entry appears in the Terminal where you’re running watch.js:
    ./file.txt updated Mon Oct 16 2023 00:44:19 GMT+0100 (British Summer Time)
  6. While we’re here, we can make the timestamp more readable. To do this, we’re going to make use of the Intl.DateTimeFormat object. It is a built-in JavaScript utility to manipulate dates and times.
  7. Add and change the following lines to format the date using Intl.DateTimeFormat:
      const formattedTime = new Intl.DateTimeFormat('en-
        GB', {
        dateStyle: 'full',
        timeStyle: 'long'
      }).format(current.mtime);
      return console.log(`${file} updated
        ${formattedTime}`);
  8. Rerun the program and make further edits to file.txt—observe that the time is now in a more readable format for your time zone:
    $ node watch.js
    ./file.txt updated Monday 16 October 2024 at 00:45:27 BST

How it works…

In the recipe, we used the watchFile() function to watch for changes on a given file. The function accepts three arguments—a filename, an optional list of options, and a listener function. The options object can include the following:

  • BigInt: The BigInt object is a JavaScript object that allows you to represent larger numbers more reliably. This defaults to false; when set to true, the numeric values returned from the object of Stats would be specified as BigInt.
  • persistent: This value indicates whether the Node.js process should continue to run while files are still being watched. It defaults to true.
  • interval: The interval value controls how often the file should be polled for changes, measured in milliseconds. The default value is 5,007 milliseconds when no interval is supplied.

The listener function supplied to the watchFile() function will execute every time a change is detected. The listener function’s arguments, current and previous are both Stats objects, representing the current and previous state of the file.

Our listener function passed to watchFile() is executed each time a change has been detected in the file being watched. Every time our updated function returns true, it logs the updated message to stdout.

The Node.js fs module provides another function, watch(), which watches for changes in files but can also watch for directories. This function differs from watchFile() as it utilizes the operating system’s underlying file system notification implementation rather than polling for changes.

Although faster and more reliable than the watchFile() API, the Watch API is not consistent across various platforms. This is because the Watch API is dependent on the underlying operating system’s method of notifying file system changes. The Node.js API documentation goes into more detail about the limitations of the Watch API across different platforms: https://nodejs.org/docs/latest/api/fs.html#fs_availability.

The watch() function similarly accepts three parameters—the filename, an array of options, and a listener function. The options that can be passed via the options parameter are as follows:

  • persistent: The persistent option is a Boolean that indicates whether the Node.js process should continue to run while files are still being watched. By default, the persistent option is set to true.
  • recursive: The recursive option is another Boolean that allows the user to specify whether changes in subdirectories should be watched – by default, this value is set to false. The recursive option is only supported on macOS and Windows operating systems.
  • encoding: The encoding option is used to specify which character encoding should be used for the filename specified—the default is utf8.
  • Signal: An AbortSignal object that can be used to cancel file watching.

The listener function that is passed to the watch() API is slightly different from the listener function passed to the watchFile() API. The arguments to the listener function are eventType and trigger, where eventType is either change or rename and trigger is the file that triggered an event. The following code represents a similar task to what we implemented in our recipe but using the Watch API:

const fs = require('node:fs');
const file = './file.txt';
fs.watch(file, (eventType, filename) => {
  const formattedTime = new Intl.DateTimeFormat('en-GB',
  {
    dateStyle: 'full',
    timeStyle: 'long'
  }).format(new Date());
  return console.log(`${filename} updated
    ${formattedTime}`);
});

The final steps of the recipe cover usage of the comprehensive Intl.DateTimeFormat utility for manipulating dates and times. Refer to MDN Web Docs for a list of available formats and APIs on Intl.DateTimeFormat: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat.

Important note

The moment.js library was once a go-to library for date manipulation and formatting in JavaScript. However, with the advancement of modern JavaScript, built-in functionalities such as Intl.DateTimeFormat offers similar capabilities natively. Additionally, moment.js has been put into maintenance mode by its maintainers, meaning no new features will be added. Coupled with concerns about its bundle size, many developers are finding moment.js no longer necessary for their projects and are instead using built-in functionalities or more modern alternative libraries.

There’s more…

The nodemon utility is a popular npm module utility for Node.js that automatically restarts your application when it detects code change. Instead of manually stopping and starting the server after each code change, nodemon handles it automatically.

Typical installation and usage of nodemon are as follows:

$ npm install --global nodemon // globally install nodemon
$ nodemon app.js // nodemon will watch for updates and restart

More recent versions of Node.js (later than v18.11.0) have a built-in watch-mode capability. To enable watch mode, you supply the --watch command-line process flag:

$ node --watch app.js

While in watch mode, modifications to the observed files trigger a Node.js process restart. By default, the built-in watch mode will monitor the main entry file and any modules that are required or imported.

It is also possible to specify the exact files you wish to watch with the --watch-path command-line process flag:

$ node --watch-path=./src --watch-path=./test app.js

More information can be found in the Node.js API documentation: https://nodejs.org/dist/latest-v22.x/docs/api/cli.html#--watch.

See also

  • The Adopting new JavaScript syntax in Node.js 22 recipe in Chapter 1
Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Explore the latest Node.js 22 features to stay at the forefront of modern development
  • Learn to build, debug, and deploy Node.js applications flawlessly
  • Gain expertise in Fastify Web Framework, Node.js module creation, and advanced testing techniques
  • Purchase of the print or Kindle book includes a free PDF eBook

Description

Node.js is a game-changing technology for building modern web applications and tooling, bringing the power of JavaScript to the server and enabling full-stack development in a unified language. This updated edition of this Node.js cookbook, featuring Node.js 22, equips you with the latest advancements and enhancements in the Node.js framework ecosystem. From Bethany Griggs, who has served on the Node.js Technical Steering Committee and participated in the Node.js Release Working Group, and Manuel Spigolon, a core maintainer of Fastify, comes this fifth edition of the Node.js Cookbook to help you master Node.js 22. This book guides you step by step through crafting reusable code with Node.js modules, streamlining development using the Fastify web framework, and implementing data persistence in databases and rigorous testing practices for robust applications. You’ll address security concerns, optimize performance with worker threads, deploy Node.js microservices using containerization and orchestration, and tackle troubleshooting with effective debugging strategies. Packed with real-world examples, this guide empowers you to harness Node.js 22's full potential for creating secure, performant, and dynamic applications.

Who is this book for?

If you have basic knowledge of JavaScript or another programming language and want to build a solid understanding of Node.js, this book is for you. It provides the foundational knowledge you need to navigate the Node.js and npm ecosystem and start building applications. For readers with some Node.js experience, it offers the opportunity to deepen and expand their skills, while beginners can use practical recipes to quickly acquire a strong foundation in Node.js concepts and features.

What you will learn

  • Grasp Node.js' async/sync programming to optimize code execution
  • Build your coding skills from scratch by creating basic Node.js apps
  • Debug and troubleshoot Node.js apps proficiently, ensuring smooth functionality
  • Deploy apps confidently to production, reaching a wider user base
  • Harness Fastify for web development to craft efficient frameworks
  • Develop Node.js modules and enhance code reusability and project structure

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Nov 15, 2024
Length: 456 pages
Edition : 5th
Language : English
ISBN-13 : 9781804614105
Languages :
Tools :

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
OR
Modal Close icon
Payment Processing...
tick Completed

Billing Address

Product Details

Publication date : Nov 15, 2024
Length: 456 pages
Edition : 5th
Language : English
ISBN-13 : 9781804614105
Languages :
Tools :

Packt Subscriptions

See our plans and pricing
Modal Close icon
€18.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
€189.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts
€264.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts
Banner background image

Table of Contents

14 Chapters
Chapter 1: Introducing Node.js 22 Chevron down icon Chevron up icon
Chapter 2: Interacting with the File System Chevron down icon Chevron up icon
Chapter 3: Working with Streams Chevron down icon Chevron up icon
Chapter 4: Using Web Protocols Chevron down icon Chevron up icon
Chapter 5: Developing Node.js Modules Chevron down icon Chevron up icon
Chapter 6: Working with Fastify – The Web Framework Chevron down icon Chevron up icon
Chapter 7: Persisting to Databases Chevron down icon Chevron up icon
Chapter 8: Testing with Node.js Chevron down icon Chevron up icon
Chapter 9: Dealing with Security Chevron down icon Chevron up icon
Chapter 10: Optimizing Performance Chevron down icon Chevron up icon
Chapter 11: Deploying Node.js Microservices Chevron down icon Chevron up icon
Chapter 12: Debugging Node.js Chevron down icon Chevron up icon
Index Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook? Chevron down icon Chevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website? Chevron down icon Chevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook? Chevron down icon Chevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support? Chevron down icon Chevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks? Chevron down icon Chevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook? Chevron down icon Chevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.