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:
writeFileSync
useful in Node.js?writeFileSync
in Node.js
writeFileSync
in Node.jswriteFileSync
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.
writeFileSync
in Node.jsThe writeFileSync
function is a pretty straightforward fs
method. It takes in three parameters, based on which it creates and writes files:
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.
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
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 last parameter of the writeFileSync
method is an object with three optional properties:
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.
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 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()); })
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:
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.
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.
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.
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.
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. Start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.