Node.js File System: Complete Guide to the fs Module
이 글의 핵심
Node.js fs module guide: sync vs async APIs, fs.promises, read/write JSON, directories, streams, watch, chokidar, errors, and performance—essential for Express and CLI tools.
Introduction
What is the fs module?
fs is Node’s built-in API for files and directories. Capabilities:
- Read/write files
- Create/remove directories
statmetadata- Watch for changes
- Stream large data
Three styles: synchronous (
*Sync), callback, and Promise (fs.promises) — prefer Promises withasync/awaitfor server code.
1. Reading files
Synchronous (blocking)
const fs = require('fs');
try {
const data = fs.readFileSync('file.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err.message);
}
Avoid on request handlers—it blocks the event loop.
Callback
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) return console.error(err.message);
console.log(data);
});
Promises (recommended)
const fs = require('fs').promises;
async function readFile() {
try {
const data = await fs.readFile('file.txt', 'utf8');
console.log(data);
} catch (err) {
if (err.code === 'ENOENT') console.error('Missing file');
else if (err.code === 'EACCES') console.error('Permission denied');
else console.error(err.message);
}
}
readFile();
Encoding and buffers
const text = await fs.readFile('text.txt', 'utf8');
const buf = await fs.readFile('image.png'); // Buffer
const asString = buf.toString('utf8');
const base64 = buf.toString('base64');
2. Writing files
const fs = require('fs').promises;
await fs.writeFile('out.txt', 'Hello, Node.js!', 'utf8');
await fs.appendFile('out.txt', '\nMore', 'utf8');
JSON helpers
async function readJSON(file) {
try {
const data = await fs.readFile(file, 'utf8');
return JSON.parse(data);
} catch (err) {
if (err.code === 'ENOENT') return null;
throw err;
}
}
async function writeJSON(file, obj) {
await fs.writeFile(file, JSON.stringify(obj, null, 2), 'utf8');
}
3. File metadata and operations
const stats = await fs.stat('file.txt');
console.log(stats.size, stats.isFile(), stats.isDirectory());
await fs.copyFile('a.txt', 'b.txt');
await fs.rename('b.txt', 'c.txt');
await fs.unlink('c.txt');
await fs.chmod('file.txt', 0o755); // Unix
Existence check
async function exists(file) {
try {
await fs.access(file);
return true;
} catch {
return false;
}
}
4. Directories
await fs.mkdir('nested/path', { recursive: true });
const names = await fs.readdir('.');
const entries = await fs.readdir('.', { withFileTypes: true });
await fs.rm('dir', { recursive: true, force: true });
Recursive directory walk and findJSFiles patterns follow the idioms below.
5. Streams
See [async & streams post](/en/blog/nodejs-series-03-async/ for createReadStream, pipe, zlib, and Transform—the same patterns apply here.
6. Watching files
fs.watch
const watcher = fs.watch('dir', { recursive: true }, (event, filename) => {
console.log(event, filename);
});
chokidar
npm install chokidar
const chokidar = require('chokidar');
chokidar.watch('src/**/*.js').on('change', (path) => console.log('changed', path));
7. Practical examples
JSON read/write (full flow)
const fs = require('fs').promises;
async function readJSON(filename) {
try {
const data = await fs.readFile(filename, 'utf8');
return JSON.parse(data);
} catch (err) {
if (err.code === 'ENOENT') return null;
throw err;
}
}
async function writeJSON(filename, data) {
await fs.writeFile(filename, JSON.stringify(data, null, 2), 'utf8');
}
async function main() {
const users = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 }
];
await writeJSON('users.json', users);
const loaded = await readJSON('users.json');
loaded[0].age = 26;
await writeJSON('users.json', loaded);
}
main().catch(console.error);
Recursive directory walk
const fs = require('fs').promises;
const path = require('path');
async function walk(directory, onFile) {
const entries = await fs.readdir(directory, { withFileTypes: true });
for (const entry of entries) {
const full = path.join(directory, entry.name);
if (entry.isDirectory()) await walk(full, onFile);
else await onFile(full);
}
}
async function findJsFiles(root) {
const out = [];
await walk(root, async (file) => {
if (path.extname(file) === '.js') out.push(file);
});
return out;
}
Backup helper (simplified)
class BackupManager {
constructor(sourceDir, backupDir) {
this.sourceDir = sourceDir;
this.backupDir = backupDir;
}
async backup() {
const fs = require('fs').promises;
const path = require('path');
await fs.mkdir(this.backupDir, { recursive: true });
const stamp = new Date().toISOString().replace(/:/g, '-');
const destDir = path.join(this.backupDir, `backup-${stamp}`);
await fs.mkdir(destDir);
const files = await fs.readdir(this.sourceDir);
for (const name of files) {
const src = path.join(this.sourceDir, name);
const stat = await fs.stat(src);
if (stat.isFile()) {
await fs.copyFile(src, path.join(destDir, name));
}
}
return destDir;
}
}
CSV via readline (sketch)
const fs = require('fs');
const readline = require('readline');
async function parseCsv(filename) {
const stream = fs.createReadStream(filename);
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
const rows = [];
let headers = [];
let first = true;
for await (const line of rl) {
if (first) {
headers = line.split(',');
first = false;
continue;
}
const cols = line.split(',');
const obj = {};
headers.forEach((h, i) => {
obj[h.trim()] = (cols[i] || ').trim();
});
rows.push(obj);
}
return rows;
}
8. Error codes
Handle ENOENT, EACCES, EISDIR, ENOTDIR, EEXIST as in the original tables.
Safe write pattern (write temp then rename)
Atomic-ish replace by writing to file.tmp then fs.rename to final path—see the safeFileOperation helper pattern below.
9. Performance
- Tune
highWaterMarkon streams - Prefer
Promise.allfor independent reads - Stream line counts instead of loading multi-GB files fully
10. Common pitfalls
- Use
path.join(__dirname, 'file.txt')instead of fragile relative paths - Pass
'utf8'when you expect a string - Avoid
readFileSyncin Express handlers - Close streams or use
stream.pipeline/util.promisify(pipeline)
Summary
| Style | Server use | CLI / boot |
|---|---|---|
| Sync | Avoid | OK |
| Callback | Legacy | OK |
fs.promises | Preferred | Preferred |
Next steps
Resources
Related posts
- [Getting started with Node.js](/en/blog/nodejs-series-01-intro/
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Node.js fs module guide: sync vs async APIs, fs.promises, read/write JSON, directories, streams, watch, chokidar, errors… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- [Node.js Async Programming: Callbacks, Promises, and](/en/blog/nodejs-series-03-async/
- [Express.js Complete Guide: Node.js Web Framework and REST](/en/blog/nodejs-series-04-express/
- Python 파일 처리 | 읽기, 쓰기, CSV, JSON 완벽 정리
이 글에서 다루는 키워드 (관련 검색어)
Node.js, JavaScript, File System, fs, Stream, I/O 등으로 검색하시면 이 글이 도움이 됩니다.