The fs Module
The fs (file system) module is Node's built-in tool for reading, writing, updating, and deleting files and directories on disk, available in synchronous, callback, and promise-based forms.
Learn The fs Module in our free Node.js course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Node.js course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
By the end of this lesson you'll know how to read a file into a string, write and append data, work with directories, check whether a path exists, and delete files — and you'll understand when to reach for the fast non-blocking promise API instead of the simpler synchronous one.
What You'll Learn in This Lesson
1️⃣ Reading Files
Reading is the most common file operation. There are two main styles. fs.readFileSync is synchronous : it pauses your program until the file is fully read, then hands back the contents. It's easy to reason about, which is why it's fine in short scripts — but it blocks the event loop, so it's a poor fit for servers.
The modern alternative is the promise-based API : {'import from \'node:fs/promises\''} and then await readFile(...) . It does the same job without freezing everything else. In both cases, pass 'utf8' as the second argument so you get a readable string back instead of a raw Buffer of bytes.
2️⃣ Writing & Appending
Writing comes in two flavours. fs.writeFileSync(path, data) creates the file if it doesn't exist and overwrites it completely if it does — so use it carefully. The promise version, writeFile from node:fs/promises , does the same thing without blocking.
When you want to add to a file rather than replace it — think log files — reach for fs.appendFile (or appendFileSync ). It writes your text onto the end and creates the file first if it isn't there yet.
3️⃣ Directories & Existence
Files live in directories, and fs handles those too. fs.mkdir creates a folder — pass {' '} to build any missing parent folders and to avoid an error if it already exists. fs.readdir lists what's inside a folder as an array of names.
To check whether something is there, fs.existsSync(path) returns a simple true / false . For richer information, fs.stat returns metadata (size, and helpers like isDirectory() ). Finally, fs.unlink deletes a file.
Your turn. The program below works once you fill in the two blanks marked ___ . Follow the 👉 hints, then run it and compare with the expected output.
No blanks this time — just a brief and an outline to keep you on track. Write it yourself, run it, and check your output against the example in the comments. This ties together writing, appending, and reading in one short program.
📋 Quick Reference — The fs Module
Practice quiz
What do you get back from fs.readFileSync('f.txt') with no encoding argument?
- A string
- A Buffer of raw bytes
- An array of lines
- null
Answer: A Buffer of raw bytes. Without an encoding, fs returns the file's raw bytes as a Buffer; pass 'utf8' to get a string.
How do you read a file as a string?
- fs.readFileSync('f.txt', 'utf8')
- fs.readFileSync('f.txt', 'string')
- fs.readFileSync('f.txt').string()
- fs.readString('f.txt')
Answer: fs.readFileSync('f.txt', 'utf8'). Passing 'utf8' as the second argument returns readable text instead of a Buffer.
Why is fs.readFileSync a poor fit inside a busy server?
- It corrupts the file
- It cannot read large files
- It blocks the event loop until the disk finishes, so other requests wait
- It always returns a Buffer
Answer: It blocks the event loop until the disk finishes, so other requests wait. The synchronous call freezes the single event loop, stalling every other request.
Which import gives you the non-blocking, promise-based file API?
- require('node:fs').promises only
- import fs from 'node:async-fs'
- require('fs-promises')
- import { readFile } from 'node:fs/promises'
Answer: import { readFile } from 'node:fs/promises'. node:fs/promises provides promise-based functions you use with await.
What does fs.writeFileSync do if the target file already exists?
- Appends to the end
- Overwrites it completely
- Throws an error
- Creates a numbered copy
Answer: Overwrites it completely. writeFileSync creates the file or overwrites it entirely if it already exists.
Which function adds to the end of a file instead of replacing it?
- fs.appendFileSync
- fs.writeFileSync
- fs.mkdirSync
- fs.updateSync
Answer: fs.appendFileSync. appendFile/appendFileSync writes onto the end of the file, creating it first if needed.
What does { recursive: true } do in fs.mkdirSync('data/reports', { recursive: true })?
- Deletes existing folders first
- Lists the directory contents
- Creates missing parent folders and doesn't throw if it already exists
- Makes the folder read-only
Answer: Creates missing parent folders and doesn't throw if it already exists. recursive: true builds any missing parent folders and avoids an error when the folder exists.
Why is calling fs.existsSync before reading often discouraged?
- It is deprecated and removed
- It introduces a race condition and adds an extra blocking call
- It always returns true
- It deletes the file
Answer: It introduces a race condition and adds an extra blocking call. The file can change between the check and the action; better to attempt the operation and handle errors.
What does an 'ENOENT: no such file or directory' error usually mean?
- The file is locked by another process
- You lack permission to read it
- The disk is full
- The path is wrong, often relative to where you ran the process
Answer: The path is wrong, often relative to where you ran the process. ENOENT means Node found nothing at that path; build absolute paths with __dirname and path.join.
Which function deletes a file?
- fs.unlinkSync
- fs.rmdirSync
- fs.removeSync
- fs.deleteSync
Answer: fs.unlinkSync. fs.unlink/unlinkSync deletes a file and throws if the file isn't there.