How to create full path with node's fs.mkdirSync?

196,914

Solution 1

One option is to use shelljs module

npm install shelljs

var shell = require('shelljs');
shell.mkdir('-p', fullPath);

From that page:

Available options:

p: full path (will create intermediate dirs if necessary)

As others have noted, there's other more focused modules. But, outside of mkdirp, it has tons of other useful shell operations (like which, grep etc...) and it works on windows and *nix

Edit: comments suggest this doesn't work on systems that don't have mkdir cli instances. That is not the case. That's the point shelljs - create a portable cross platform set of shell like functions. It works on even windows.

Solution 2

Update

NodeJS version 10.12.0 has added a native support for both mkdir and mkdirSync to create a directory recursively with recursive: true option as the following:

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

And if you prefer fs Promises API, you can write

fs.promises.mkdir(targetDir, { recursive: true });

Original Answer

Create directories recursively if they do not exist! (Zero dependencies)

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

function mkDirByPathSync(targetDir, { isRelativeToScript = false } = {}) {
  const sep = path.sep;
  const initDir = path.isAbsolute(targetDir) ? sep : '';
  const baseDir = isRelativeToScript ? __dirname : '.';

  return targetDir.split(sep).reduce((parentDir, childDir) => {
    const curDir = path.resolve(baseDir, parentDir, childDir);
    try {
      fs.mkdirSync(curDir);
    } catch (err) {
      if (err.code === 'EEXIST') { // curDir already exists!
        return curDir;
      }

      // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows.
      if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure.
        throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`);
      }

      const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1;
      if (!caughtErr || caughtErr && curDir === path.resolve(targetDir)) {
        throw err; // Throw if it's just the last created dir.
      }
    }

    return curDir;
  }, initDir);
}

Usage

// Default, make directories relative to current working directory.
mkDirByPathSync('path/to/dir');

// Make directories relative to the current script.
mkDirByPathSync('path/to/dir', {isRelativeToScript: true});

// Make directories with an absolute path.
mkDirByPathSync('/path/to/dir');

Demo

Try It!

Explanations

  • [UPDATE] This solution handles platform-specific errors like EISDIR for Mac and EPERM and EACCES for Windows. Thanks to all the reporting comments by @PediT., @JohnQ, @deed02392, @robyoder and @Almenon.
  • This solution handles both relative and absolute paths. Thanks to @john comment.
  • In the case of relative paths, target directories will be created (resolved) in the current working directory. To Resolve them relative to the current script dir, pass {isRelativeToScript: true}.
  • Using path.sep and path.resolve(), not just / concatenation, to avoid cross-platform issues.
  • Using fs.mkdirSync and handling the error with try/catch if thrown to handle race conditions: another process may add the file between the calls to fs.existsSync() and fs.mkdirSync() and causes an exception.
    • The other way to achieve that could be checking if a file exists then creating it, I.e, if (!fs.existsSync(curDir) fs.mkdirSync(curDir);. But this is an anti-pattern that leaves the code vulnerable to race conditions. Thanks to @GershomMaes comment about the directory existence check.
  • Requires Node v6 and newer to support destructuring. (If you have problems implementing this solution with old Node versions, just leave me a comment)

Solution 3

A more robust answer is to use use mkdirp.

var mkdirp = require('mkdirp');

mkdirp('/path/to/dir', function (err) {
    if (err) console.error(err)
    else console.log('dir created')
});

Then proceed to write the file into the full path with:

fs.writeFile ('/path/to/dir/file.dat'....

Solution 4

fs-extra adds file system methods that aren't included in the native fs module. It is a drop in replacement for fs.

Install fs-extra

$ npm install --save fs-extra

const fs = require("fs-extra");
// Make sure the output directory is there.
fs.ensureDirSync(newDest);

There are sync and async options.

https://github.com/jprichardson/node-fs-extra/blob/master/docs/ensureDir.md

Solution 5

Using reduce we can verify if each path exists and create it if necessary, also this way I think it is easier to follow. Edited, thanks @Arvin, we should use path.sep to get the proper platform-specific path segment separator.

const path = require('path');

// Path separators could change depending on the platform
const pathToCreate = 'path/to/dir'; 
pathToCreate
 .split(path.sep)
 .reduce((prevPath, folder) => {
   const currentPath = path.join(prevPath, folder, path.sep);
   if (!fs.existsSync(currentPath)){
     fs.mkdirSync(currentPath);
   }
   return currentPath;
 }, '');
Share:
196,914

Related videos on Youtube

David Silva Smith
Author by

David Silva Smith

I'm an angel investor in the Lansing, MI area.

Updated on July 14, 2022

Comments

  • David Silva Smith
    David Silva Smith almost 2 years

    I'm trying to create a full path if it doesn't exist.

    The code looks like this:

    var fs = require('fs');
    if (!fs.existsSync(newDest)) fs.mkdirSync(newDest); 
    

    This code works great as long as there is only one subdirectory (a newDest like 'dir1') however when there is a directory path like ('dir1/dir2') it fails with Error: ENOENT, no such file or directory

    I'd like to be able to create the full path with as few lines of code as necessary.

    I read there is a recursive option on fs and tried it like this

    var fs = require('fs');
    if (!fs.existsSync(newDest)) fs.mkdirSync(newDest,'0777', true);
    

    I feel like it should be that simple to recursively create a directory that doesn't exist. Am I missing something or do I need to parse the path and check each directory and create it if it doesn't already exist?

    I'm pretty new to Node. Maybe I'm using an old version of FS?

    • jfriend00
      jfriend00 almost 9 years
      github.com/substack/node-mkdirp and all sorts of other solutions on this Google search.
    • Matt Parkins
      Matt Parkins over 5 years
      @AndyRay This StackOverflow question is now the top result in google for this question, which is funny because that means it's recursi....
    • MrJomp
      MrJomp about 5 years
      That was a problem on older versions of Node, updating to Node 12+ solves the problem
  • David Silva Smith
    David Silva Smith almost 9 years
    Thanks! I ended up using exec (I was already using this) and it worked like a charm. var exec = require('child_process').exec; var command = "mkdir -p '" + newDest + "'"; var options = {}; var after = function(error, stdout, stderr) { console.log('error', error); console.log('stdout', stdout); console.log('stderr', stderr); } exec(command, options, after);
  • cshotton
    cshotton over 8 years
    This option may break on node.js platforms that don't have a command line mkdir instance (i.e., non-Linux-y hosts) so it isn't portable, if that matters.
  • Ruan Mendes
    Ruan Mendes almost 8 years
    Prefer this answer since you are importing just what you need, not an entire library
  • Stephen Rauch
    Stephen Rauch over 7 years
    When giving an answer it is preferable to give some explanation as to WHY your answer is the one.
  • josebui
    josebui over 7 years
    Sorry, you are right, I think this way it is cleaner and easier to follow
  • pagep
    pagep over 7 years
    This is the best answer! Most of us already have fs-extra in the app anyway.
  • MikingTheViking
    MikingTheViking about 7 years
    Upvote for the easy, recursive response not requiring an additional library or approach!
  • bryanmac
    bryanmac about 7 years
    @cshotton - are you referring to the comment or the answer? shelljs works even on windows. exec mkdir -p (the comment) of course doesn't.
  • John
    John about 7 years
    This specifically works with relative paths. If targDir were specified as an absolute path, the split/reduce sequence would loose the context: /home/name would end up being evaluated relative the the current working directory as if it were home/name.
  • Arvin
    Arvin about 7 years
    @josebui I think it is better using "path.sep" instead of forward slash (/) to avoid environment specefic issues.
  • janos
    janos about 7 years
    Congrats on the Populist badge ;-)
  • Christopher Bull
    Christopher Bull almost 7 years
    Missing require statements: const fs = require('fs'); const path = require('path');
  • Mouneer
    Mouneer almost 7 years
    @ChristopherBull, intentionally not added just to focus on the logic, but anyways, I added them. Thanks ;)
  • moodboom
    moodboom almost 7 years
    12 lines of solid code, zero dependencies, I'll take it every time.
  • Gershom Maes
    Gershom Maes almost 7 years
    Is it necessary to check if the dir exists? Can't you just call mkdir either way, and handle the error if necessary?
  • John Q
    John Q over 6 years
    @Mouneer on Mac OS X 10.12.6, the error thrown when trying to create "/" after passing in an absolute path is "EISDIR" (Error: EISDIR: illegal operation on a directory, mkdir '/'). I think probably checking for dir existence is still the best cross-platform way to go (acknowledging it will be slower).
  • deed02392
    deed02392 over 6 years
    I suspect similar to the OS X error, it fails on Windows because there are no permissions to do a MKDIR on `C:\`. This could be handled by checking if the dir exists already.
  • Mouneer
    Mouneer over 6 years
    @JohnQ, the way I handle the existing directory is the best practice to keep ourselves safe from race conditions. Regarding permissions, I follow Terminal conversions. If no sufficient permissions then fail explicitly. I only handle the already-exists error because it affects the business; If parent directory exists, don't stop and go create children. So, If your use case involves lake of permissions, then you need to catch this exception yourself and certainly re-throw other unrelated exceptions just like what I do.
  • Mouneer
    Mouneer over 6 years
    @deed02392, check my previous comment.
  • deed02392
    deed02392 over 6 years
    @Mouneer I think it would be more useful to point out you can get around the permissions issue for all non-root non-administrator accounts if you only pass a relative path to build from. Otherwise I'm not sure how you could tell if the perm issue will matter unless you just guess the dir exists and try to create the child.
  • Mouneer
    Mouneer over 6 years
    @deed02392 forgive me, I'm not sure if I understand your point well. But let's say, If I need to handle the perm error I can try{mkDirByPathSync('/path/to/dir');} catch(e){if (PERMISSION_ERROR) {// Do something}}. And using a relative or absolute path isn't the actual reason for the perm issues; You may use a relative path but your permissions are not sufficient to create its directories. Also, you can use an absolute path to your home directory for example and create its directories normally as you have the needed permissions. So, could you please guide me to the arguing point?
  • deed02392
    deed02392 over 6 years
    @Mouneer Eg: I am a normal user and I want to create a new dir at: C:\Users\Me\Desktop. With your code, there will be a perm error trying to mkdir('C:\'). And another one trying to mkdir('C:\Users'). But these paths already exist anyway. If you passed it relative to 'Me' your would be doing .\Desktop\newdir. You will bypass the EPERM error because you own 'Desktop' (so you'll get EEXIST instead). You are saying you can't check if they exist safely before. I am suggesting that //Do something cannot be anything useful. I don't think you can provide a full example of handling EPERM.
  • Mouneer
    Mouneer over 6 years
    @deed02392 I got your point. Thanks for the example and you're right. I will make a little edit to the snippet to support this use case and let you know. Thanks.
  • deed02392
    deed02392 over 6 years
    OK, good luck, I am hoping it's possible. My feeling is it won't be very clean.
  • Mouneer
    Mouneer over 6 years
    @deed02392 what about catching the perm exception and throw it only if last nested dir failed ^_^
  • deed02392
    deed02392 over 6 years
    Do you mean throw it only if you got an EPERM when trying to create the deepest directory? That will improve it for sure.
  • Mouneer
    Mouneer over 6 years
    @deed02392, I tested the snippet with paths like /home/publicDir/test which works as expected even when I have no permissions for directories: /, /home, /home/publicDir. This is because the exception thrown for this directories is EEXIST and not EACCES nor EPERM. Try it yourself rextester.com/edit/FJCAC1817.
  • deed02392
    deed02392 over 6 years
    @Mouneer All my examples have been for my platform (Windows). There, the behaviour is EPERM gets raised as I explained :-)
  • Mouneer
    Mouneer over 6 years
    @deed02392, Is this your case rextester.com/XZI35837 ? And does this snippet helps you with your use case?
  • deed02392
    deed02392 over 6 years
    @Mouneer It works fine on rextester, but I haven't tested it on Windows. Can you do that or do you need me to?
  • Mouneer
    Mouneer over 6 years
    @deed02392 No, never mind thanks for your comments ^_^
  • McAden
    McAden over 6 years
    Works fine in windows for me. However, I had to change const sep = path.sep because on windows that will give a ` but in my gulp scripts all my paths are forward /`.
  • Mouneer
    Mouneer over 6 years
    As per Docs: "path.sep provides the platform-specific path segment separator: \ on Windows or / on POSIX". So, no need to change it. @McAden
  • McAden
    McAden over 6 years
    @Mouneer Right, that was the problem I'm on Windows, but I'm using /
  • robyoder
    robyoder about 6 years
    @Mouneer not sure what happened here, but creating absolute paths still doesn't work for me. I'm on macOS, and I get Error: EISDIR: illegal operation on a directory, mkdir '/'.
  • Stepan Rafael
    Stepan Rafael about 6 years
    Thanks. It is the far best method.
  • John Vandivier
    John Vandivier about 6 years
    nifty trick: return targetDir.split(sep)... so you can have the path for your file operation after ensuring the folder
  • Playdome.io
    Playdome.io about 6 years
    files on windows are handled with backslash not forward slash. Your code simply won't work there. C:\data\test ...
  • Hamiora
    Hamiora about 6 years
    Edited but suggest you validate your comment. On node try the following and see what happens var fs = require('fs') fs.mkdirSync('test') fs.mkdirSync('test\\test1') fs.mkdirSync('test/test2')
  • Playdome.io
    Playdome.io about 6 years
    Whatever you're saying.., my down vote still stays until you learn to write better code.
  • Hamiora
    Hamiora about 6 years
    Haha. Ok, I'll work really hard on learning how to write better code. BTW most answers above, including the OP, use forward slashes. Suggest you stop trolling.
  • Playdome.io
    Playdome.io about 6 years
    Simply put path.delimiter instead of forward slash instead of arguing? Or handle both in the code so then you may get upvoted.
  • Pete Alvin
    Pete Alvin about 6 years
    I wish creating all necessary intermediate directories was built-in.
  • Peter T.
    Peter T. about 6 years
    I had the problem that this function failed on Windows as it tried to mkdir "C:\" which failed with "EPERM" (not "EEXIST"), so I modified the function as if (err.code !== 'EEXIST' && (path.sep === "\\" && ! curDir.match(/^[a-z]:\\$/i))) { to handle this special case in windows. Maybe you want to include this in your code above.
  • Qwertie
    Qwertie almost 6 years
    People often use / as a path separator on Windows so that whatever they have written will work on Unix/Linux. So refusing to accept / on Windows isn't good. Most Windows commands and programs accept both.
  • Almenon
    Almenon almost 6 years
    @Mouneer that fixes the windows error, thanks :) I would also suggest putting in the following line to fix the error on mac: if(curDir == '/') return '/' see github.com/Almenon/AREPL-vscode/blob/…
  • Mouneer
    Mouneer almost 6 years
    @JohnQ Check the updated answer. It's supporting platform-specific errors like EISDIR for Mac. Thanks for reporting. :)
  • Mouneer
    Mouneer almost 6 years
    @deed02392 Check the updated answer. It's supporting platform-specific errors like EPERM and EACCES for Windows. Thanks for reporting :)
  • Mouneer
    Mouneer almost 6 years
    @robyoder Check the updated answer. It's supporting platform-specific errors like EISDIR for Mac Thanks for reporting :)
  • Mouneer
    Mouneer almost 6 years
    @Almenon Check the updated answer. It's supporting platform-specific errors like EISDIR for Mac and EPERM and EACCES for Windows. Thanks for reporting :)
  • Avius
    Avius almost 6 years
    Good solution. I wrote a unit test for this which helped me spot a tiny mistake in this line: if (!caughtErr || caughtErr && targetDir === curDir) {. I am on Windows and if I try to construct an absolute path like so mkDirByPathSync('/path/to/dir');, and get an error so that caughtErr is true, then targetDir will be \path\to\dir while curDir wil be C:\path\to\dir and the condition check will fail (if I understand the intended behaviour correctly). Fix would perhaps be using path.resolve(targetDir).
  • rdm
    rdm almost 6 years
    When I try this, path.delimiter is undefined. (meanwhile, '/' works on windows)
  • Avius
    Avius over 5 years
    Both path.resolve(targetDir) and path.resolve(baseDir, targetDir) seem to work equally well for me and my tests : ]
  • Mouneer
    Mouneer over 5 years
    Since the path is absolute, both path.resolve(targetDir) and path.resolve(baseDir, targetDir) will work similarly. @Avius Generally I updated the answer to cover your use case. Thanks for reporting :)
  • nurettin
    nurettin over 5 years
    I found the related pull request github.com/nodejs/node/pull/23313
  • Choco Li
    Choco Li over 5 years
    It will throw error when directory exist and stop. Use a try catch block can make it keep creating other non-exist folder.
  • Qwertie
    Qwertie over 5 years
    Was the downvote for supporting Windows correctly? Did I mention it works on other OSs too?
  • ChiralMichael
    ChiralMichael over 5 years
    I am pretty late to the party, but I just have a note that your comment about the race condition is superfluous. You still have a race condition (another process may remove the directory between calls to mkdir). I'm always a fan of having exceptions be exceptional. It clarifies intent and also limits the performance impact of throwing an exception. Do the natural thing and bail if the directory gets created underneath you. That's the expected behavior anyways.
  • Mouneer
    Mouneer over 5 years
    @ChiralMichael, thanks for expressing you point of view. Regarding You still have a race condition, the scenario you're talking about is what already handled by code now. Regarding Do the natural thing, the natural thing here is to create the directory. This is the main and the only usage of the function. So, when creating a directory, I expect some failures with the core logic that I should handle. Those failures become handled now and not unhandled. Any other failures that I expect and I cannot handle - like wrong permissions - are left to be bailed normally.
  • Илья Зеленько
    Илья Зеленько over 5 years
    You can use this cool function with Promise or callback of your choice.
  • ChiralMichael
    ChiralMichael over 5 years
    @Mouneer Allow me to restate. There is a race in the iterations of reduce(): an EEXIST error on one iteration, then the directory deleted from under you in another process, then mkdir on the next iteration. In accounting for the race condition of two processes creating the same directory, you've simply pushed the problem around. My second comment: "do the natural thing" is to mean that I would expect any create directory method to fail if the directory was created underneath it. Finally, you structured you algorithm so that an exception (EEXIST) is an expected workflow. This is to be avoided.
  • schnatterer
    schnatterer over 5 years
    This would be great if it offered a possibility to use memfs for unit testing. It doesn't :-( github.com/jprichardson/node-fs-extra/issues/274
  • Josh Slate
    Josh Slate over 5 years
    path.sep is coming through as either / or \\ for me. path.delimiter is : or ;.
  • Rich Apodaca
    Rich Apodaca over 5 years
    This should be the accepted answer. It does not throw if the directory already exists, and can be used with async/await via fs.promises.mkdir.
  • ZzZombo
    ZzZombo over 5 years
    @Hamiora: did you post code without testing? There is a lot of blatant errors that make it simply not to work at all... All those shadowed variables... The horror!
  • Mouneer
    Mouneer over 5 years
    @ChiralMichael, then mkdir on the next iteration what would happen? I would expect any create directory method to fail if the directory was created underneath it.I prefer not to fail especially when the directory is a parent directory /parent1/parent2/child I would - in contrary - thank the second process instead :). It's a business decision anyway.
  • Hamiora
    Hamiora over 5 years
    Hey @zzzombo. My original answer was edited by other contributors, so I don't really consider this my answer anymore. But I wrote and lifted it from my own working code when I posted it, and plus or minus the one shadowed variable, the logic is still sound. Suggest to copy the logic, not the code, but thanks for comment.
  • drorw
    drorw about 5 years
  • Nika Kasradze
    Nika Kasradze about 5 years
    this is not a solution, this is an alternative to the solution. context: pics.onsizzle.com/…
  • Karim
    Karim about 4 years
    good solution because doesn't require node >=10 like the other answers
  • bkwdesign
    bkwdesign over 3 years
    if i pass in a path /does/not/exist it only creates the first level does folder ;-(
  • cyborg
    cyborg over 3 years
    Ok, I don't know why but I would have thought that would be extremely easy to see why in your case in debugging.
  • bryanmac
    bryanmac almost 3 years
    @NikaKasradze this is a possible solution and works. All solutions are alternatives.
  • bryanmac
    bryanmac almost 3 years
    @cshotton, this works on platforms where mkdir is not present (windows). That is the point of shelljs - to creata a portable shell experience. Did you try it?