Working with the File System in Node.js

· 3 min read · Updated March 7, 2026 · beginner
node.js fs file-system beginner

Node.js comes with a powerful built-in module for working with the file system: the fs module. Whether you need to read configuration files, write logs, process uploaded files, or build entire file-based databases, the fs module has you covered.

In this tutorial, you’ll learn how to read files, write files, work with directories, and handle common file system operations using both callbacks and promises.

Reading Files

The simplest way to read a file is with fs.readFile. Here’s how to read a text file:

const fs = require('fs');

fs.readFile('config.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('Error reading file:', err);
    return;
  }
  console.log('File contents:', data);
});

The callback receives the error first (Node.js convention), then the file contents. The second argument 'utf8' tells Node.js to decode the buffer as a UTF-8 string.

Reading Files with Promises

If you prefer async/await syntax, use fs/promises:

const fs = require('fs/promises');

async function readConfig() {
  try {
    const data = await fs.readFile('config.json', 'utf8');
    const config = JSON.parse(data);
    console.log('Config loaded:', config);
  } catch (err) {
    console.error('Error reading config:', err);
  }
}

readConfig();

Synchronous Reading

For scripts where async doesn’t matter, use the synchronous versions:

const fs = require('fs');

const data = fs.readFileSync('config.txt', 'utf8');
console.log('Contents:', data);

Warning: Avoid synchronous file operations in production servers—they block the event loop and can cause performance issues.

Writing Files

Writing files works similarly with fs.writeFile:

const fs = require('fs/promises');

async function saveData() {
  const data = { name: 'Alice', age: 30 };
  await fs.writeFile('user.json', JSON.stringify(data, null, 2));
  console.log('Data saved!');
}

saveData();

By default, writeFile overwrites the file. To append instead:

const fs = require('fs/promises');

async function appendToLog(message) {
  const timestamp = new Date().toISOString();
  await fs.appendFile('app.log', `[${timestamp}] ${message}\n`);
}

appendToLog('Application started');

Working with Directories

Create directories with fs.mkdir:

const fs = require('fs/promises');

async function setupProject() {
  await fs.mkdir('src/utils', { recursive: true });
  await fs.mkdir('src/components', { recursive: true });
  console.log('Directories created!');
}

setupProject();

The recursive: true option prevents errors if the directory already exists.

List directory contents with fs.readdir:

const fs = require('fs/promises');

async function listFiles(dir) {
  const files = await fs.readdir(dir);
  console.log('Files:', files);
}

listFiles('./src');

Checking File Stats

Get file metadata with fs.stat:

const fs = require('fs/promises');

async function fileInfo(filepath) {
  const stats = await fs.stat(filepath);
  console.log('Is file:', stats.isFile());
  console.log('Is directory:', stats.isDirectory());
  console.log('Size:', stats.size, 'bytes');
  console.log('Created:', stats.birthtime);
  console.log('Modified:', stats.mtime);
}

fileInfo('config.json');

Deleting Files and Directories

Remove files with fs.unlink:

const fs = require('fs/promises');

async function cleanup() {
  await fs.unlink('temp.txt');
  console.log('File deleted');
}

cleanup();

Remove directories with fs.rmdir:

const fs = require('fs/promises');

async function removeDir(dir) {
  await fs.rmdir(dir, { recursive: true });
  console.log('Directory removed');
}

removeDir('old-folder');

Working with Paths

Always use the path module for cross-platform path handling:

const path = require('path');

const filePath = path.join(__dirname, 'config', 'settings.json');
console.log('Full path:', filePath);

const filename = path.basename('/home/user/documents/report.pdf');
console.log('Filename:', filename); // 'report.pdf'

const ext = path.extname('image.png');
console.log('Extension:', ext); // '.png'

Streams for Large Files

For large files, use streams to avoid loading everything into memory:

const fs = require('fs');

const readStream = fs.createReadStream('large-file.txt', 'utf8');
const writeStream = fs.createWriteStream('copy.txt');

readStream.on('data', (chunk) => {
  writeStream.write(chunk);
});

readStream.on('end', () => {
  console.log('File copied!');
});

readStream.on('error', (err) => {
  console.error('Error:', err);
});

Or use pipeline for cleaner handling:

const fs = require('fs');
const { pipeline } = require('stream/promises');

async function copyFile(src, dest) {
  const readStream = fs.createReadStream(src);
  const writeStream = fs.createWriteStream(dest);
  await pipeline(readStream, writeStream);
  console.log('Copy complete!');
}

copyFile('large-file.txt', 'copy.txt');

Summary

You’ve learned the fundamentals of working with Node.js file system:

  • Read files with fs.readFile or fs/promises
  • Write files with fs.writeFile and append with fs.appendFile
  • Create directories with fs.mkdir (use recursive: true)
  • List contents with fs.readdir
  • Get metadata with fs.stat
  • Delete with fs.unlink and fs.rmdir
  • Use streams for large files to avoid memory issues

The fs/promises API is recommended for modern Node.js applications—it works seamlessly with async/await and is easier to reason about.

In the next tutorial, you’ll learn how to build an HTTP server with Node.js.