The Node.js file system module provides a good number of methods to create files, write to files, and update files. Amongst these methods, there is one that can perform all three functions synchronously, which is writeFileSync
.
In this article, we will look at the writeFileSync
function and how to use it in Node.js. We will cover the following:
- Why is
writeFileSync
useful in Node.js? - How to use
writeFileSync
in Node.js - Catching errors while using
writeFileSync
in Node.js - Creating new files based on user input
- Updating files in Node with
writeFileSync
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, and the only difference in the way they work is in catching and handling errors.
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!');
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 amongst 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 { Buffer } = require('buffer'); 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 can be converted back to a string with the toString
method. If you’re only dealing with strings a simpler approach for strings 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
.
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.
For 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 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:
a
: creates the file (if it does not exist) or appends to the existing data (if it does exist)ax
andwx
: 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 { Buffer } = require('buffer'); 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.
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:
- Create a new file for every user with their username as the unique identifier
- Alert the user to try a different username if the given username already exists
With that, our app code would be as follows:
const { Buffer } = require('buffer'); 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 here; 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.
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 { Buffer } = require('buffer'); 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.
As mentioned, 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
is in catching and handling the errors; otherwise, all parameters mentioned are available in both functions.
Happy hacking, and thanks for reading.
The post Using the <code>writeFileSync</code> method in Node.js appeared first on LogRocket Blog.
from LogRocket Blog https://ift.tt/MgcVP2Y
via Read more