296

I've been tinkering with Node.js and found a little problem. I've got a script which resides in a directory called data. I want the script to write some data to a file in a subdirectory within the data subdirectory. However I am getting the following error:

{ [Error: ENOENT, open 'D:\data\tmp\test.txt'] errno: 34, code: 'ENOENT', path: 'D:\\data\\tmp\\test.txt' }

The code is as follows:

var fs = require('fs');
fs.writeFile("tmp/test.txt", "Hey there!", function(err) {
    if(err) {
        console.log(err);
    } else {
        console.log("The file was saved!");
    }
}); 

Can anybody help me in finding out how to make Node.js create the directory structure if it does not exits for writing to a file?

1
  • 15
    fs.promises.mkdir(path.dirname("tmp/test.txt"), {recursive: true}).then(x => fs.promises.writeFile("tmp/test.txt", "Hey there!")) Commented Nov 5, 2018 at 18:43

12 Answers 12

328

Node > 10.12.0

fs.mkdir now accepts a { recursive: true } option like so:

// Creates /tmp/a/apple, regardless of whether `/tmp` and /tmp/a exist.
fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => {
  if (err) throw err;
});

Or with a promise:

fs.promises.mkdir('/tmp/a/apple', { recursive: true }).catch(console.error);

Notes:

  1. In many cases, you should use fs.mkdirSync rather than fs.mkdir

  2. Including a trailing slash is harmless / has no effect

  3. mkdirSync/mkdir do nothing, harmlessly, if the directory already exists (there’s no need to check for existence)

Node <= 10.11.0

You can solve this with a package like mkdirp or fs-extra. If you don't want to install a package, please see Tiago Peres França's answer below.

Sign up to request clarification or add additional context in comments.

3 Comments

note that fs.promises is still experimental nodejs.org/dist/latest-v10.x/docs/api/…
If you use this because you're trying to create a local directory temp file, you must pass in './tmp/a/apple'
@rom , "cars/ford/mustang" will also appear in the current local directory.
193

If you don't want to use any additional package, you can call the following function before creating your file:

var path = require('path'),
    fs = require('fs');

function ensureDirectoryExistence(filePath) {
  var dirname = path.dirname(filePath);
  if (fs.existsSync(dirname)) {
    return true;
  }
  ensureDirectoryExistence(dirname);
  fs.mkdirSync(dirname);
}

14 Comments

This should use statSync instead of existsSync, based on stackoverflow.com/questions/4482686/…
path is also a package that needs to be required just as fs: var path = require('path') in case anyone is wondering. See node documentation.
There's been some confusion about whether the function fs.existsSync has been deprecated or not. At first, by my understanding, I thought it was, so I updated the answer to reflect this. But now, as pointed by @zzzzBov, the documentation clearly states that only fs.exists has been deprecated, the use of fs.existsSync is still valid. For this reason I deleted the previous code and my answer now contains only the simpler solution (with the use of fs.existsSync).
@chrismarx imagine the following path: "/home/documents/a/b/c/myfile.txt". "/home/documents" exists, while everything in front of it doesn't. When "ensureDirectoryExistence" is called for the 1st time, dirname is "/home/documents/a/b/c". I can't call fs.mkdirSync(dirname) right now because "/home/documents/a/b" also doesn't exist. To create the directory "c", I need to first ensure the existence of "/home/documents/a/b".
|
86

With node-fs-extra you can do it easily.

Install it

npm install --save fs-extra

Then use the outputFile method. Its documentation says:

Almost the same as writeFile (i.e. it overwrites), except that if the parent directory does not exist, it's created.

You can use it in four ways.

Async/await

const fse = require('fs-extra');

await fse.outputFile('tmp/test.txt', 'Hey there!');

Using Promises

If you use promises, this is the code:

const fse = require('fs-extra');

fse.outputFile('tmp/test.txt', 'Hey there!')
   .then(() => {
       console.log('The file has been saved!');
   })
   .catch(err => {
       console.error(err)
   });

Callback style

const fse = require('fs-extra');

fse.outputFile('tmp/test.txt', 'Hey there!', err => {
  if(err) {
    console.log(err);
  } else {
    console.log('The file has been saved!');
  }
})

Sync version

If you want a sync version, just use this code:

const fse = require('fs-extra')

fse.outputFileSync('tmp/test.txt', 'Hey there!')

For a complete reference, check the outputFile documentation and all node-fs-extra supported methods.

Comments

28

Shameless plug alert!

You will have to check for each directory in the path structure you want and create it manually if it doesn't exist. All the tools to do so are already there in Node's fs module, but you can do all of that simply with my mkpath module: https://github.com/jrajav/mkpath

8 Comments

will that create the file directly or just the directory structure? I'm looking for a solution which create the file along with the directory structure when creating the file.
Just the directory structure. You would the mkdir/path first and, if there weren't any errors, proceed to writing your file. It would be simple enough to write a function to do both simultaneously, given a full path to a file to write - just split off the filename using path.basename
In fact, it was so simple that I wrote it in 2 minutes. :) (Untested)
Update: Tested and edited, please try it again if it didn't work the first time.
@Kiyura How is this different from the widely used mkdirp?
|
19

Same answer as above, but with async await and ready to use!

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

async function isExists(path) {
  try {
    await fs.access(path);
    return true;
  } catch {
    return false;
  }
};

async function writeFile(filePath, data) {
  try {
    const dirname = path.dirname(filePath);
    const exist = await isExists(dirname);
    if (!exist) {
      await fs.mkdir(dirname, {recursive: true});
    }
    
    await fs.writeFile(filePath, data, 'utf8');
  } catch (err) {
    throw new Error(err);
  }
}

Example:

(async () {
  const data = 'Hello, World!';
  await writeFile('dist/posts/hello-world.html', data);
})();

1 Comment

Amazing answer.
11

Since I cannot comment yet, I'm posting an enhanced answer based on @tiago-peres-frança fantastic solution (thanks!). His code does not make directory in a case where only the last directory is missing in the path, e.g. the input is "C:/test/abc" and "C:/test" already exists. Here is a snippet that works:

function mkdirp(filepath) {
    var dirname = path.dirname(filepath);

    if (!fs.existsSync(dirname)) {
        mkdirp(dirname);
    }

    fs.mkdirSync(filepath);
}

3 Comments

That's because @tiago's solution expects a file path. In your case, abc is interpreted as the file you need to create a directory for. To also create the abc directory, add a dummy file to your path, e.g. C:/test/abc/dummy.txt.
Use recursive: fs.promises.mkdir(path.dirname(file), {recursive: true}).then(x => fs.promises.writeFile(file, data))
@Offenso it's the best solution, but for Node.js version 10.12 and above only.
10

My advise is: try not to rely on dependencies when you can easily do it with few lines of codes

Here's what you're trying to achieve in 14 lines of code:

fs.isDir = function(dpath) {
    try {
        return fs.lstatSync(dpath).isDirectory();
    } catch(e) {
        return false;
    }
};
fs.mkdirp = function(dirname) {
    dirname = path.normalize(dirname).split(path.sep);
    dirname.forEach((sdir,index)=>{
        var pathInQuestion = dirname.slice(0,index+1).join(path.sep);
        if((!fs.isDir(pathInQuestion)) && pathInQuestion) fs.mkdirSync(pathInQuestion);
    });
};

3 Comments

Wouldn't the third line be better like this? return fs.lstatSync(dpath).isDirectory(), otherwise what would happen if isDirectory() returns false?
Use recursive: fs.promises.mkdir(path.dirname(file), {recursive: true}).then(x => fs.promises.writeFile(file, data))
@Offenso it's not supported by a node 8
8

As simple as this, create recursively if does not exist:

if (!fs.existsSync(dir)) {
  fs.mkdirSync(dir, { recursive: true })
}

Comments

2

I just published this module because I needed this functionality.

https://www.npmjs.org/package/filendir

It works like a wrapper around Node.js fs methods. So you can use it exactly the same way you would with fs.writeFile and fs.writeFileSync (both async and synchronous writes)

Comments

1

IDK why, but this mkdir always make an extra directory with the file name if also the filename is included in the path; e.g.

// fileName = './tmp/username/some-random.jpg';
try {
  mkdirSync(fileName, { recursive: true })
} catch (error) {};

enter image description here

Solution

It can be solved this way: fileName.split('/').slice(0, -1).join('/') which only make directories up to the last dir, not the filename itself.

// fileName = './tmp/username/some-random.jpg';
try {
  mkdirSync(fileName.split('/').slice(0, -1).join('/'), { recursive: true })
} catch (error) {};

enter image description here

1 Comment

just use const fileDir = path.dirname(fileName)
0

Here is a complete solution to create the folder with all the needed subfolders, and then writing the file, all in one function.

This is an example assuming you are creating a backup file and you want to pass the backup folder name in the function (hence the name of the variables)


  createFile(backupFolderName, fileName, fileContent) {
    const csvFileName = `${fileName}.csv`;

    const completeFilePath = join(process.cwd(), 'backups', backupFolderName);

    fs.mkdirSync(completeFilePath, { recursive: true });

    fs.writeFileSync(join(completeFilePath, csvFileName), fileContent, function (err) {
      if (err) throw err;
      console.log(csvFileName + ' file saved');
    })
  }

Comments

0

Although it my answer won't cover all the use-cases, there is an elegant way for creating files in a newly created unique folder inside os temp dir:

import { mkdtempSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';

const filePath = join(mkdtempSync(join(tmpdir(), 'workspace-')), 'file.txt')

// write file

This example takes advantage of the fact that mkdtempSync returns path to the newly created dir.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.