File System in Node.js Part 2: Reading and Writing Files

March 14, 2021

We can use Node.js to create/write files and read data from them.

When we write data to a file, the file ends up getting created if it doesn't already exist. That's why we are using the words writing and creating interchangeably. We will first be using the writeFileSync function to write data to a file in a synchronous way.

const fs = require("fs");

const filePath = "temp.txt";
const fileData = "hello world";

fs.writeFileSync(filePath, fileData);

When we run this program, we create a file called temp.txt with the content hello world. Notice that rerunning this program doesn't result in any errors. We end up overwriting the content of the previous file.

Let's now look into reading files. For this purpose, we will be using the readFileSync function.

const fs = require("fs");

const filePath = "temp.txt";

const data = fs.readFileSync(filePath);
console.log(data);

This program would read the contents of the temp.txt, assuming that a file with that name exists in the current folder. The result that we are getting from this operation is not in the form that we might have expected. We are receiving something called a Buffer that looks like this:

<Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>

To get the result of this operation as a string of text instead of a string of a Buffer, we need to specify an encoding value of utf8 to the readFileSync function. Encoding value defines how we want the content of this file to be interpreted. We are working with a text file, so the utf8 encoding value helps us to interpret the content as such.

const fs = require("fs");

const filePath = "temp.txt";

const data = fs.readFileSync(filePath, "utf8");
console.log(data);

Now we are getting the content of the file as a string of text. This function would throw an error if the file we are trying to read from didn't exist. We could first check to see if the file exists just like we did when working with folders, but a better way of doing it is just trying to do the operation anyway and catch the error if it happens. We can achieve this by using a JavaScript structure called try...catch.

Error Handling in JavaScript

try...catch is a built-in JavaScript structure much like if-else that allows us to handle an error if it happens.

To explore how this works, let's first examine how to throw an error in JavaScript. We could do so by using the throw statement.

throw "42";

throw statement throws an error with the given value. In this case, the error we throw will have the value of the string 42.

There is a built-in JavaScript object called Error object that we can use to define our errors. This code below creates an Error object with the given string as the error message.

const message = "This is the error message";
const error = new Error(message);
console.log(error.message);

throw error;

console.log("End of Program");

As we can see, we can access the string value that we pass into the Error object during the creation through the Error object's message property. This value can be seen on the screen when the error is thrown.

Note that the rest of the program does not get executed after an error is encountered. The program exits immediately if there is an unhandled error. We can handle errors using the try...catch structure.

Let's see how try...catch works now that we know how to create error conditions deliberately.

const genericError = new Error("Something went wrong");

try {
  throw genericError;
} catch (errorObj) {
  console.log(errorObj);
}

console.log("End of program");

Here in this example, we are defining a generic error and deliberately throwing that error inside the try block of the try...catch structure. The catch block that comes after the try receives the error object and lets us do whatever we would like to do with it. Here we have chosen to handle the error by console.logging it instead of throwing that error. This way, we can see that an error has happened but not let it take our program down. We can verify that the program continues to function until the end since we see the final console.log message displayed on the screen.

Let's use try...catch statements to update our previous code.

Reading Files with Error Handling

Previously we had this program read the contents of a file, but it would crash if the target file didn't exist.

const fs = require("fs");

const filePath = "temp.txt";

const data = fs.readFileSync(filePath, "utf8");
console.log(data);

Let's update our code so that we can handle the error that gets thrown if the file does not exist.

const fs = require("fs");

const readFileSync = (filePath) => {
  try {
    const data = fs.readFileSync(filePath, "utf8");
    return data;
  } catch (err) {
    console.log("Something went wrong");
    console.log(err);
  }

  return null;
};

const main = () => {
  const filePath = "temp.txt";
  const data = readFileSync(filePath);
  console.log("Data contained in the file is:");
  console.log(data);
};

main();

First, we are wrapping the readFile functionality inside a function to make it a bit more readable and reusable. Then inside this function, we are using the try...catch block. We return the content of a target file if we can read that file without any errors. In case there are any errors, we return a null value from this function.

const readFileSync = (filePath) => {
  try {
    const data = fs.readFileSync(filePath, "utf8");
    return data;
  } catch (err) {
    console.log("Something went wrong");
    console.log(err);
  }

  return null;
};

We are then creating a function called main to contain the main body of operations that we will be executing. Inside the main function, we are making use of this readFile function to read the file's contents at the given path. We then log the contents to the screen with a descriptive text at the beginning.

Error handling is an essential aspect of writing resilient programs. try...catch is a powerful structure in JavaScript that allows us to handle errors in ways that suits our purposes. When we are writing a program, sometimes we want to throw an error and cause a crash since we might be operating in an environment that doesn't tolerate any errors. For example, if we have a program that handles business transactions, it might be better to stop the program from executing than to have it work with faulty values. But in other cases, we might want to ignore errors and continue with the execution. try...catch allows us to have control over what to do when an error happens.

Summary

We have just learned about how to read and write files in Node.js synchronously. We have also learned about the try...catch statement that allows us to handle errors. When not handled, errors would make our programs crash but using the try...catch statement, we can choose to handle those errors in any way we want.

Find me on Social Media