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.jswriteFileSyncThe Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
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.
writeFileSyncWe 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.
Monitor failed and slow network requests in productionDeploying 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 lets you replay user sessions, eliminating guesswork around why bugs happen by showing exactly what users experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings — compatible with all frameworks.
LogRocket's Galileo AI watches sessions for you, instantly identifying and explaining user struggles with automated monitoring of your entire product experience.
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.

Examine AgentKit, Open AI’s new tool for building agents. Conduct a side-by-side comparison with n8n by building AI agents with each tool.

AI agents powered by MCP are redefining interfaces, shifting from clicks to intelligent, context-aware conversations.

Learn how platform engineering helps frontend teams streamline workflows with Backstage, automating builds, documentation, and project management.

Build an AI assistant with Vercel AI Elements, which provides pre-built React components specifically designed for AI applications.
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 now