Introduction: Why Working with the File System is Important in Node.js
In modern web development, it's rare to get by without interacting with the file system. Whether it's uploading user avatars, generating logs, processing CSV reports, or reading configuration files — Node.js provides a powerful built-in fs (File System) module for these tasks.
Unlike browser JavaScript, where file access is strictly limited for security purposes, Node.js gives you full control over the server's file system. In this article, we'll break down key operations with practical examples that will help you confidently work with files and folders.
What you will learn:
- How to read and write files (synchronously and asynchronously)
- How to work with Streams for large files
- How to create, rename, and delete folders
- How to properly handle errors and paths
1. Basics: Reading and Writing Files
The fs module offers three main approaches to working with files: synchronous, asynchronous (with callbacks), and promise-based. It is recommended to use the asynchronous promise-based approach (fs.promises) — it does not block the Event Loop and is easier to read.
1.1 Reading a File
The simplest way to read an entire file is to use readFile.
const fs = require('fs').promises;
async function readConfig() { try { const data = await fs.readFile('./config.json', 'utf-8'); const config = JSON.parse(data); console.log('Port:', config.port); } catch (err) { console.error('Read error:', err.message); }}
readConfig();Important: Specify the encoding ('utf-8'), otherwise a Buffer object will be returned.
1.2 Writing to a File
For writing, use writeFile (overwrites the file) or appendFile (appends to the end).
const fs = require('fs').promises;
async function saveLog(message) { const logLine = `[${new Date().toISOString()}] ${message}`; try { await fs.appendFile('./app.log', logLine); console.log('Log saved'); } catch (err) { console.error('Write error:', err); }}
saveLog('Server started');2. Working with Large Files: Streams
Imagine you need to read a 2 GB file. The readFile method will load it entirely into RAM, which can lead to memory leaks. For such cases, there are streams (fs.createReadStream and fs.createWriteStream).
A stream processes data in parts (chunks), without storing the entire file in memory.
Example: Copying a Large File
const fs = require('fs');const path = require('path');
const source = path.join(__dirname, 'bigfile.zip');const destination = path.join(__dirname, 'copy.zip');
const readStream = fs.createReadStream(source);const writeStream = fs.createWriteStream(destination);
readStream.on('data', (chunk) => { console.log(`Read ${chunk.length} bytes`); writeStream.write(chunk);});
readStream.on('end', () => { writeStream.end(); console.log('Copying completed');});
readStream.on('error', (err) => { console.error('Read error:', err);});Tip: For copying, it's more convenient to use the built-in pipeline method from the stream module — it automatically handles errors and closes streams.
3. Managing Directories
Working with folders is an equally important part. Node.js allows you to create, read, and delete directories.
3.1 Creating and Checking a Folder
const fs = require('fs').promises;
async function ensureDir(dirPath) { try { await fs.mkdir(dirPath, { recursive: true }