File system operations in NodeJS

Sync vs async operations

Node offers both, sync and async methods to handle files. Sync methods (having sync in the method name, e.g. fs.readFileSync()) will block the execution of the program until it has finished processing, so it is recommend to either always use async operations or at least for heavy operations, such as handling big files.

Comparing stat, lstat and fstat

  • stat follows symlinks. When given a path that is a symlink, it returns the stat of the target of the symlink.
  • lstat doesn’t follow symlinks. When given a path that is a symlink it returns the stat of the symlink and not its target.
  • fstat takes a file descriptor rather than a path.

Check if resource exists


const fs = require('fs');
const path = require('path');
const dirname = path.join('E:', 'myFolder'');

/* Sync #1 */

/* Sync #2 */
try {
    const stat = fs.statSync(dirname);
} catch (exc) {


const fs = require('fs');
const path = require('path');
const dirname = path.join('E:', 'myFolder'');

/* Async #1 */
fs.access(dirname, (err, res) => {
   if(err) {

/* Async #2 */
fs.stat(dirname, (err, res) => {
    if(err) {

Check type of existing resource

The following method will check if an existing resource is of a specific type, it will throw an error if the resource does not exist. To check for existing use fs.existsSync(dirname) (see above) instead.

// checks type of a resource that MUST exist or throws error

.isSymbolicLink() // (only valid with fs.lstat())

Processing files within directories

Files within a single directory (not subdirectories)

const fs = require('fs');
const path = require('path');
const dirname = path.join('E:', 'myFolder');

const files = fs.readdirSync(dirname)

files.forEach((file) => {

Files including subdirectories with limited depth (sync)

function getFiles(dirPath, currentLevel, maxLevel) {
    if (currentLevel > maxLevel) {
    } else {
        fs.readdirSync(dirPath).forEach(function (file) {
            let filePath = path.join(dirPath, file);
            let stat = fs.statSync(filePath);
            if (stat.isDirectory()) {
      `Dir: ${filePath}`);
                getFiles(filePath, currentLevel + 1, maxLevel);
            } else {
      `File: ${filePath}`);

Building JS object tree from directory including subdirectories (async)

If you have a folder structure like this:

 - directoryA
  - file1
  - file2
 - directoryB
  - file1
  - file2
  - file3

…then the following code will create an object such as this:

  'directoryA' : {
    file1: true,
    file2: true
  'directoryB' : {
    file1: true,
    file2: true,
    file3: true
const fs = require("fs");

const basePath = process.argv[2]; //Getting the path (it works)
const result = {};

//Function to check my path is exist and it's a directory
const isDirectory = async (path) => {
  try {
    const stats = await fs.promises.lstat(path);
    return stats.isDirectory();
  } catch (error) {
    throw new Error("No such file or Directory");

//Recursive function that should create the object tree of the file system
const createTree = async (path, target) => {
  const data = await fs.promises.readdir(path);
  for (const item of data) {
    const currentLocation = `${path}/${item}`;
    const isDir = await isDirectory(currentLocation);
    if (!isDir) {
      target[item] = true;
    target[item] = {};
    await createTree(currentLocation, target[item]);

//Consuming the createTree function
(async () => {
  try {
    await createTree(basePath, result);
  } catch (error) {

Watching files in a directory

const fs = require('fs');
const path = require('path');
const dirname = path.join('E:', 'myFolder');, (evt, filename) => {
    if(evt === 'rename') {
        console.log(`${filename} was renamed`);

Get parts of a file path (file name, extension etc.)

import Path from 'path'

// Getting the file name without extension
Path.parse('/home/user/avatar.png').name; // -> 'avatar'

// Getting just the extension
Path.parse('/home/user/avatar.png').ext; // -> 'png'

// Getting file name with extension
Path.basename('/home/user/avatar.png'); // -> 'avatar.png'

About Author

Mathias Bothe To my job profile

I am Mathias, born 39 years ago in Heidelberg, Germany. Today I am living in Munich and Stockholm. I am a passionate IT freelancer with more than 15 years experience in programming, especially in developing web based applications for companies that range from small startups to the big players out there. I am founder of, creator of the security service platform BosyProtect© and initiator of several other software projects.