Back to Blog
Lesson 9 of the Node.js: Build Your First Server & CLI course
Node.jsJuly 27, 20264 min read

Synchronous File I/O in Node.js: Reading and Writing Files

Master the Node.js fs module for synchronous I/O. Learn how to read and write files reliably while handling common errors in your backend scripts.

Node.jsfs modulesynchronous I/Ofile systemreadFileSyncbackend development
Close-up of stacked binders filled with documents for office or educational use.

Previously in this course, we learned how to manipulate file paths using the Working with the Path Module in Node.js for Cross-Platform Apps. Now that we can reliably locate files on our system, this lesson adds the ability to interact with their contents using the fs module.

In Node.js, "synchronous" operations mean your program execution pauses until the file system task is finished. While we generally prefer non-blocking code for web servers, synchronous I/O is perfectly acceptable—and often preferred—for simple CLI scripts, configuration loading, or setup tasks where order of execution is critical.

Understanding the fs Module

The built-in fs (File System) module provides an API for interacting with your hard drive. To use it, you simply require it at the top of your file.

JAVASCRIPT
const fs = require(CE9178">'fs');

When using synchronous methods, you'll notice they all end with the Sync suffix. This naming convention is a clear signal that the operation will block the event loop—the topic we covered in Understanding the Node.js Architecture.

Reading Files with readFileSync

The fs.readFileSync method is the standard way to grab the contents of a file. It takes a file path as the first argument and an optional encoding as the second. If you omit the encoding, Node.js returns a raw Buffer object (binary data); passing 'utf8' gives you a standard string.

JAVASCRIPT
const fs = require(CE9178">'fs');
const path = require(CE9178">'path');

const filePath = path.join(__dirname, CE9178">'data.txt');
const content = fs.readFileSync(filePath, CE9178">'utf8');

console.log(content);

Writing Files with writeFileSync

Writing is just as straightforward. The fs.writeFileSync method takes the file path, the data to write, and an optional object for configuration (like flags).

JAVASCRIPT
const fs = require(CE9178">'fs');

const data = CE9178">'Hello, Node.js!';
fs.writeFileSync(CE9178">'output.txt', data, CE9178">'utf8');

console.log(CE9178">'File saved successfully.');

Handling File System Errors

Neatly arranged blue office binders labeled with dates and names for organized storage.

Synchronous methods do not use callbacks. Instead, they throw exceptions if something goes wrong (e.g., the file doesn't exist or you lack write permissions). To prevent your entire application from crashing, always wrap these calls in a try...catch block.

ScenarioPotential ErrorSolution
Reading missing fileENOENTVerify existence before reading
Writing to protected dirEACCESCheck user permissions
Path is a directoryEISDIRValidate path is a file first

Worked Example: A Robust Configuration Loader

Let’s combine these concepts into a practical utility that reads a configuration file and updates a log file.

JAVASCRIPT
const fs = require(CE9178">'fs');

function updateSystemLog(message) {
  try {
    // 1. Read existing log
    let logs = CE9178">'';
    if (fs.existsSync(CE9178">'system.log')) {
      logs = fs.readFileSync(CE9178">'system.log', CE9178">'utf8');
    }

    // 2. Append new message
    const newLog = CE9178">`${logs}\n${new Date().toISOString()}: ${message}`;

    // 3. Write back to disk
    fs.writeFileSync(CE9178">'system.log', newLog, CE9178">'utf8');
  } catch (err) {
    console.error(CE9178">'Failed to update log file:', err.message);
  }
}

updateSystemLog(CE9178">'Server started.');

Hands-on Exercise

  1. Create a file named todo.txt in your project folder with one line of text.
  2. Write a script that reads todo.txt synchronously.
  3. Append a new item to that content string.
  4. Write the updated content back to todo.txt using fs.writeFileSync.
  5. Wrap your logic in a try...catch block to handle potential errors gracefully.

Common Pitfalls

Close-up of a rusty sewer manhole cover in a grassy Boston park.

  • Blocking the Event Loop: Never use readFileSync inside an Express route handler. Because it blocks the thread, your server will stop responding to all other users until the disk I/O completes.
  • Forgetting 'utf8': If you forget the encoding argument, you will receive a Buffer (e.g., <Buffer 48 65 6c 6c 6f>). If you see strange hexadecimal output, check your encoding settings.
  • Sync vs Async: Beginners often try to mix sync and async patterns. Stick to one style per function to avoid confusing race conditions.

FAQ

Q: When should I use synchronous I/O? A: It is ideal for CLI tools, startup scripts (like loading config.json), or any task that must finish before the next line of code executes.

Q: Is fs.readFileSync slower than asynchronous methods? A: It’s not necessarily "slower" in terms of raw disk throughput, but it is "blocking." It prevents the process from doing other work while waiting for the disk.

Q: Can I use fs.writeFileSync to append to a file? A: By default, it overwrites the file. To append, you must either read the file first (as shown above) or use the { flag: 'a' } option in the write function.

Recap

Wooden Scrabble tiles spelling 'RECAP' on a brown textured background. Text concept image.

You’ve learned the basics of synchronous file interaction. We covered fs.readFileSync for reading, fs.writeFileSync for writing, and the importance of try...catch blocks for error management. These tools are the foundation for building scripts that persist data.

Up next: We will explore Asynchronous File I/O, where we'll learn how to handle file operations without blocking your server's performance.

Similar Posts