Elijah Agbonze I am a full-stack software developer from Nigeria. I love coding and writing about coding.

Using the writeFileSync method in Node.js

8 min read 2296

Using The Writefilesync Method In Node Js

The Node.js file system module provides a good number of methods to create files, write to files, and update files. Among these methods, there is one that can perform all three functions synchronously: writeFileSync.

In this article, we’ll look at the writeFileSync function and we’ll demonstrate how to use it in Node.js. We’ll cover the following:

Why is writeFileSync useful in Node.js?

Before we jump into a practical demonstration of the writeFileSync function, let’s look at why we may want to create and write files synchronously.

Like any other synchronous function in Node, writeFileSync will block the event loop until the operation is completed or until it fails. In other words, it blocks the execution of any other statements until its execution fails or completes.

For example, when a writeFileSync function is called, every other statement after it will have to wait until the function creates a new file or throws an error for not creating a new file:

fs.writeFileSync('index.txt', 'Some content');
console.log('file created');

In the code above, if the index.txt file is created successfully, the next line of code will execute. But if it fails, Node will throw an error, giving you the opportunity to catch the error.

Blocking the event loop, or main thread, is really not a good practice for real-world applications. It is considered a bad practice in Node because it will reduce performance and cause security risks.

Creating and writing files with writeFileSync is only recommended for debugging purposes, just like every other synchronous function in Node. As such, you should use the asynchronous function writeFile for creating and writing files in real-world projects.

Knowing how to use the writeFileSync function can help you easily get started with the writeFile function because they have similar parameters. The only difference in the way they work is in catching and handling errors.

The writeFile has a callback that returns an err parameter when an error occurs. However, in writeFileSync, you’ll have to catch errors manually with a try``…``catch statement as we’ll see later in this article.

How to use writeFileSync in Node.js

The writeFileSync function is a pretty straightforward fs method. It takes in three parameters, based on which it creates and writes files:

  • The file name or descriptor
  • The data that you want to write to the file
  • Options: a string or object you can use to specify three additional optional parameters

Of these three parameters, only two are required. The “options” parameter is optional. In the example below, the file name is index.txt, and the data to be written to the file is Hello World!:

const fs = require('fs');

fs.writeFileSync('index.txt', 'Hello World!');
console.log('File created');

Not only does writeFileSync create files, but it can also overwrite or append to the data on any existing file. Let’s take a closer look at the parameters used in this function.

The file name parameter

You can use writeFileSync in Node to create any file type — including a text file, an HTML file, JavaScript file, Markdown file, Python file, etc. — as long as you write the file name with the right extension. For example,

fs.writeFileSync('index.txt', 'Hello World!');

The index.txt file in the example above will be created in the current directory you’re in. You can specify a path to some other directory, like so:

fs.writeFileSync('notes/index.txt', 'Hello World!');

N.B., Node will throw an error if the notes directory does not already exist; this is because the writeFileSync method cannot create a directory in Node

The data parameter

We have only used strings as our data in all of the examples so far, but in real-world projects, you may be dealing with data other than strings. Using the Buffer class in Node is common among developers, so let’s take a look at it.

In Node, the Buffer class is a global type for dealing with binary data directly. The easiest way of constructing a new Buffer for any data is by allocating a specific size of bytes, like so:

const fs = require('fs');
const rawData = 'Hello World';
const data = Buffer.alloc(rawData.length, rawData, 'utf8');
fs.writeFileSync('index.txt', data);

The first parameter of the Buffer.alloc method represents the size of the byte. In the above example, we used the length of the string.

Using the length of the string isn’t always safe, as this number does not account for the encoding that is used to convert the string into bytes. Instead, we could use another Buffer method, like so:

const rawData = 'Hello World';
const data = Buffer.alloc(Buffer.byteLength(rawData, 'utf8'), rawData, 'utf8');

Note that the default encoding for strings is utf8, so we can safely remove the encoding for both methods:

const rawData = 'Hello World';
const data = Buffer.alloc(Buffer.byteLength(rawData), rawData);
console.log(data); // <Buffer 48 65 6c 6c 6f 20 57 6f 72 6c 64>
console.log(data.toString()) // Hello World

A Buffer class containing a text can be converted back to a string with the toString method. If you’re only dealing with strings a much simpler approach would be to use the Buffer.from(string, encoding) method:

const rawData = 'Hello World';
const data = Buffer.from(rawData, 'utf8');
console.log(data); // <Buffer 48 65 6c 6c 6f 20 57 6f 72 6c 64>
console.log(data.toString()) // Hello World

With the Buffer.from method, you can easily encode a string, array, or buffer without having to worry about specifying the size of the bytes, like in the case of Buffer.alloc. The default encoding for the Buffer.from method is also utf8.

Encoding an array is as easy as encoding a string using the Buffer.from method. It can take in an array of integers (specifically Uint8Array which ranges from 0-255) or an array of octets. Entries that exceed 255 or that go below 0 are truncated to fit:

const buf = Buffer.from([1, 2, 3, 4, 5]);
console.log(buf); // <Buffer 01 02 03 04 05>
console.log(buf.at(-1)) // 5

// creates a new buffer from utf8 string 'john'
console.log(Buffer.from([0x6a, 0x6f, 0x68, 0x6e]));

In some cases, you could have an array of objects that needs to be converted into binary data. Passing the array into the Buffer.from method won’t work. Instead, you can convert the array into a JSON string like so:

const users = [
  { id: 1, name: 'John', age: 12 },
  { id: 2, name: 'Mary', age: 9 },
];

console.log(Buffer.from(JSON.stringify(users)).toString()); // [{"id":1,"name":"John","age":12},{"id":2,"name":"Mary","age":9}]

The options parameter

The last parameter of the writeFileSync method is an object with three optional properties:

  • Encoding
  • Mode
  • Flag

The encoding property has a default of utf8. The encoding of the Buffer class used as the data in the writeFileSync method will override the encoding here.

If you specify a utf8 encoding for your data and mistakenly specify base64 encoding here, the utf8 will override the base64 encoding. So if you’re going to often use the Buffer class to encode your data, you don’t need to specify any value for the encoding property here.



Here’s an example:

const rawData = 'Hello World';
const data = Buffer.from(rawData, 'utf8');
fs.writeFileSync('index.txt', data, { encoding: 'base64' }) // utf8 will override this encoding
const txtFile = fs.readFileSync('index.txt')
console.log(txtFile.toString()) // Hello World

The mode property sets the file mode (permission and sticky bits), and it only has an effect on newly created files. The default mode is 0o666.

The flag property controls how the file will be created and written on. The default flag is w, which creates the file (if the file does not already exist) or overwrites whatever data the file has with the new data (if the file does already exist).

Other flags are as follows:

  • a: creates the file (if it does not exist) or appends to the existing data (if it does exist)
  • ax and wx: creates the file (if it does not exist) or throws an error (if it already exists)

Here is a full list of the flags in Node.

Catching errors while using writeFileSync in Node.js

To catch errors we use the try...catch statement. To show this example, we will have to create a custom error. Assuming we already have a file, such as an index.txt file, we could say:

const fs = require('fs');

try {
  const rawData = 'Hello World';
  const data = Buffer.from(rawData);
  fs.writeFileSync('index.txt', data, { flag: 'ax' });
} catch (e) {
  console.log(e); // will log an error because file already exists
}

This will throw an error because the ax flag only creates new files; it cannot append to existing files. So, if you’re trying to write to an existing file with the ax or wx flags, you will get an error.

Reading a simple file in Node.js

Reading a file in Node can be done synchronously or asynchronously, that is readFileSync and readFile respectively. As mentioned previously, using a synchronous function in Node.js is only recommended for development.

Just like writing files, the difference between readFileSync and readFile lies in how errors are handled. The readFileSync method needs manual handling while readFile uses a callback to handle and return any error.

Here’s an example using readFileSync:

const fs = require('fs');

try {
  const file = fs.readFileSync('notes/index.txt'); // pass in the path to the file
  console.log(file.toString()) // it returns a buffer, convert it back to string
} catch (e) {
  console.log(e) // logs any error encountered with reading the file
}

Here’s an example using readFile:

const fs = require('fs');

fs.readFile('notes/index.txt', (err, data) => {
  if (err) console.log(err);

  console.log(data.toString());
})

Creating new files based on user input

Let’s say we want to only create new files, write to them, and move on. For example, we may have an app for which we want to create a file for every new user input from the terminal, with each file having a unique identifier — the username.

Based on this information about the app’s functionality, we need to:

  1. Create a new file for every user with their username as the unique identifier
  2. Alert the user to try a different username if the given username already exists

With that, our app code would be as follows:

const fs = require('fs');
const readline = require('readline');
const { stdin: input, stdout: output } = require('process');
const rl = readline.createInterface({ input, output });

// input username
function requestUsername() {
  rl.question('Enter username: ', (username) => {
    try {
      if (!username) return requestUsername();
      const data = Buffer.from(`Your username is ${username}`);
      fs.writeFileSync(`${username}.txt`, data, { flag: 'ax' });
    } catch (e) {
      if (e.code === 'EEXIST') {
        console.log(`${username} already exists, enter a different username`);
        return requestUsername();
      }
    }
    rl.close();
  });
}
requestUsername();

Don’t worry about all the extra details; the real takeaway here is the ax flag and try...catch statement. The ax flag helps us figure out if the username already exists, in which case the try...catch statement helps alert the user.


More great articles from LogRocket:


Updating files in Node.js with writeFileSync

We obviously cannot update a file using the w, ax, or wx flags, but what we can use is the a flag. The a flag, as mentioned before, not only appends to a file, but also creates the file if it does not exist.

Expanding on our previous example, let’s say we want to add to the data of a user without creating a new file for that user. We would write the following code:

const fs = require('fs');
const readline = require('readline/promises');
const { stdin: input, stdout: output } = require('process');
const rl = readline.createInterface({ input, output });

async function getUsername() {
  const username = await rl.question('Enter username: ');
  if (!username) return getUsername();

  try {
    fs.readFileSync(`${username}.txt`);
    return username;
  } catch (e) {
    if (e.code === 'ENOENT') {
      console.log(
        `Username "${username}" does not exist, try a different username`
      );
      return getUsername();
    }
  }
}
async function updateUserInfo() {
  const username = await getUsername();
  const rawData = await rl.question('Enter user info (name|age|course): ');
  const data = Buffer.from(`\n${rawData}\n`);
  fs.writeFileSync(`${username}.txt`, data, { flag: 'a' });
  rl.close();
}
updateUserInfo();

In the code above, we prompt the user for the username and check if a file exists for that user. If a file does exist, we request more information from that user and then update the user’s file. Pretty simple.

There is a similar flag, r+, that throws an error if a file does not exist, but reads and writes to the file if it does exist. However, that flag won’t fit into what we want here because it overrides all data in the file rather than appending to it like the a flag.

Conclusion

In this article, we learned how to use writeFileSync to create and write files with Node, which is useful for debugging.

We saw how to pass in data with the Buffer class using two common methods, how to control how the file will be created using flags, and also how to catch errors. We also learned how to read a simple file with readFile and readFileSync which can be handy when writing files.

With what you’ve learned so far, you can also easily get started with using writeFile (creating files asynchronously). The only difference between writeFile and writeFileSync lies in catching and handling errors; otherwise, all parameters mentioned are available in both functions.

Happy hacking, and thanks for reading.

200’s only Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .
Elijah Agbonze I am a full-stack software developer from Nigeria. I love coding and writing about coding.

Leave a Reply