273

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?

David Silva Smith
  • 11,498
  • 11
  • 67
  • 91
  • 1
    https://github.com/substack/node-mkdirp and all sorts of other solutions on [this Google search](https://www.google.com/search?q=node+js+create+directory+recursively&rlz=1C1CHFX_enUS515US515&oq=nodejs+create+directory&aqs=chrome.2.69i57j0l5.8399j1j7&sourceid=chrome&es_sm=93&ie=UTF-8). – jfriend00 Jul 27 '15 at 05:58
  • 5
    @AndyRay This StackOverflow question is now the top result in google for this question, which is funny because that means it's recursi.... – Matt Parkins Jan 31 '19 at 14:47
  • 1
    That was a problem on older versions of Node, updating to Node 12+ solves the problem – MrJomp Jun 20 '19 at 16:33

23 Answers23

733

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)
Jason C
  • 38,729
  • 14
  • 126
  • 182
Mouneer
  • 12,827
  • 2
  • 35
  • 45
  • 13
    Upvote for the easy, recursive response not requiring an additional library or approach! – MikingTheViking Apr 19 '17 at 21:20
  • 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`. – John May 30 '17 at 23:53
  • 1
    Missing require statements: const fs = require('fs'); const path = require('path'); – Christopher Bull Jul 06 '17 at 20:07
  • 1
    @ChristopherBull, intentionally not added just to focus on the logic, but anyways, I added them. Thanks ;) – Mouneer Jul 07 '17 at 12:05
  • 2
    12 lines of solid code, zero dependencies, I'll take it every time. – moodboom Jul 08 '17 at 18:35
  • Is it necessary to check if the dir exists? Can't you just call `mkdir` either way, and handle the error if necessary? – Gershom Maes Sep 18 '17 at 18:26
  • 1
    @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). – John Q Feb 11 '18 at 23:05
  • 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. – deed02392 Feb 12 '18 at 15:51
  • @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 Feb 12 '18 at 16:55
  • @deed02392, check my previous comment. – Mouneer Feb 12 '18 at 16:55
  • @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. – deed02392 Feb 12 '18 at 19:08
  • @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? – Mouneer Feb 12 '18 at 20:41
  • 1
    @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. – deed02392 Feb 17 '18 at 22:13
  • @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. – Mouneer Feb 18 '18 at 08:24
  • OK, good luck, I am hoping it's possible. My feeling is it won't be very clean. – deed02392 Feb 18 '18 at 18:29
  • @deed02392 what about catching the perm exception and throw it only if last nested dir failed ^_^ – Mouneer Feb 18 '18 at 19:13
  • Do you mean throw it only if you got an EPERM when trying to create the deepest directory? That will improve it for sure. – deed02392 Feb 19 '18 at 15:07
  • @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 http://rextester.com/edit/FJCAC1817. – Mouneer Feb 19 '18 at 16:54
  • @Mouneer All my examples have been for my platform (Windows). There, the behaviour is EPERM gets raised as I explained :-) – deed02392 Feb 22 '18 at 00:01
  • @deed02392, Is this your case http://rextester.com/XZI35837 ? And does this snippet helps you with your use case? – Mouneer Feb 26 '18 at 16:47
  • @Mouneer It works fine on rextester, but I haven't tested it on Windows. Can you do that or do you need me to? – deed02392 Feb 26 '18 at 17:51
  • @deed02392 No, never mind thanks for your comments ^_^ – Mouneer Feb 26 '18 at 19:03
  • 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 `/`. – McAden Mar 28 '18 at 19:30
  • As per Docs: "`path.sep` provides the platform-specific path segment separator: `\ ` on Windows or `/` on POSIX". So, no need to change it. @McAden – Mouneer Mar 29 '18 at 13:24
  • 1
    @Mouneer Right, that was the problem I'm on Windows, **but** I'm using `/` – McAden Mar 29 '18 at 16:42
  • 1
    @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 '/'`. – robyoder May 10 '18 at 19:46
  • nifty trick: `return targetDir.split(sep)...` so you can have the path for your file operation after ensuring the folder – John Vandivier May 30 '18 at 12:37
  • I wish creating all necessary intermediate directories was built-in. – Pete Alvin Jun 26 '18 at 16:29
  • 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. – Peter T. Jun 29 '18 at 12:05
  • 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. – Qwertie Jul 06 '18 at 21:04
  • @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 https://github.com/Almenon/AREPL-vscode/blob/244458c815ec13e1c85238e294763cebbd27065c/src/registerAreplDump.ts#L53 – Almenon Jul 29 '18 at 22:49
  • @JohnQ Check the updated answer. It's supporting platform-specific errors like EISDIR for Mac. Thanks for reporting. :) – Mouneer Jul 30 '18 at 12:04
  • @deed02392 Check the updated answer. It's supporting platform-specific errors like EPERM and EACCES for Windows. Thanks for reporting :) – Mouneer Jul 30 '18 at 12:07
  • 1
    @robyoder Check the updated answer. It's supporting platform-specific errors like EISDIR for Mac Thanks for reporting :) – Mouneer Jul 30 '18 at 12:10
  • @Almenon Check the updated answer. It's supporting platform-specific errors like EISDIR for Mac and EPERM and EACCES for Windows. Thanks for reporting :) – Mouneer Jul 30 '18 at 12:14
  • 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)```. – Avius Sep 04 '18 at 09:53
  • 1
    Both ```path.resolve(targetDir)``` and ```path.resolve(baseDir, targetDir)``` seem to work equally well _for me_ and my tests : ] – Avius Oct 15 '18 at 08:20
  • 1
    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 :) – Mouneer Oct 15 '18 at 11:01
  • 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. – ChiralMichael Nov 27 '18 at 17:54
  • @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. – Mouneer Nov 29 '18 at 11:22
  • @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. – ChiralMichael Dec 03 '18 at 18:07
  • @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. – Mouneer Jan 23 '19 at 18:27
89

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'....
Maxime Pacary
  • 22,336
  • 11
  • 85
  • 113
cshotton
  • 2,784
  • 2
  • 14
  • 11
57

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.

bryanmac
  • 38,941
  • 11
  • 91
  • 99
  • 2
    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); – David Silva Smith Jul 27 '15 at 13:29
  • 33
    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. – cshotton Feb 03 '16 at 15:15
  • 1
    @cshotton - are you referring to the comment or the answer? shelljs works even on windows. exec mkdir -p (the comment) of course doesn't. – bryanmac Apr 25 '17 at 12:19
  • You can use [this cool function](https://github.com/jprichardson/node-fs-extra/blob/master/docs/ensureDir.md) with Promise or callback of your choice. – Илья Зеленько Nov 30 '18 at 19:53
  • 1
    this is not a solution, this is an alternative to the solution. context: https://pics.onsizzle.com/heckoverflow-how-doia-bork2121-999-9999you-do-b-thedudehimselt-you-do-31502340.png – Nika Kasradze Jun 03 '19 at 14:45
  • 2
    @NikaKasradze this is a possible solution and works. All solutions are alternatives. – bryanmac Jul 07 '21 at 22:29
  • @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? – bryanmac Jul 07 '21 at 22:29
51

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

schnatterer
  • 7,525
  • 7
  • 61
  • 80
Deejers
  • 3,087
  • 2
  • 23
  • 20
  • 5
    This is the best answer! Most of us already have fs-extra in the app anyway. – pagep Mar 19 '17 at 14:06
  • This would be great if it offered a possibility to use `memfs` for unit testing. It doesn't :-( https://github.com/jprichardson/node-fs-extra/issues/274 – schnatterer Dec 14 '18 at 12:32
31

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;
 }, '');
josebui
  • 429
  • 5
  • 5
  • 4
    When giving an answer it is preferable to give [some explanation as to WHY your answer](http://stackoverflow.com/help/how-to-answer) is the one. – Stephen Rauch Feb 01 '17 at 01:04
  • Sorry, you are right, I think this way it is cleaner and easier to follow – josebui Feb 04 '17 at 00:40
  • 4
    @josebui I think it is better using "path.sep" instead of forward slash (/) to avoid environment specefic issues. – Arvin Jun 07 '17 at 11:06
  • good solution because doesn't require node >=10 like the other answers – Karim Jun 22 '20 at 10:23
30

This feature has been added to node.js in version 10.12.0, so it's as easy as passing an option {recursive: true} as second argument to the fs.mkdir() call. See the example in the official docs.

No need for external modules or your own implementation.

Capaj
  • 4,024
  • 2
  • 43
  • 56
  • 1
    I found the related pull request https://github.com/nodejs/node/pull/23313 – nurettin Oct 19 '18 at 12:44
  • 1
    It will throw error when directory exist and stop. Use a try catch block can make it keep creating other non-exist folder. – Choco Li Oct 26 '18 at 00:39
  • 1
    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. – Rich Apodaca Jan 14 '19 at 22:09
12

i know this is an old question, but nodejs v10.12.0 now supports this natively with the recursive option set to true. fs.mkdir

// Creates /tmp/a/apple, regardless of whether `/tmp` and /tmp/a exist.
fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => {
  if (err) throw err;
});
We Are All Monica
  • 13,000
  • 8
  • 46
  • 72
Nelson Owalo
  • 2,324
  • 18
  • 37
6

Now with NodeJS >= 10.12.0, you can use fs.mkdirSync(path, { recursive: true }) fs.mkdirSync

We Are All Monica
  • 13,000
  • 8
  • 46
  • 72
William Penagos
  • 141
  • 2
  • 3
3

Example for Windows (no extra dependencies and error handling)

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

let dir = "C:\\temp\\dir1\\dir2\\dir3";

function createDirRecursively(dir) {
    if (!fs.existsSync(dir)) {        
        createDirRecursively(path.join(dir, ".."));
        fs.mkdirSync(dir);
    }
}

createDirRecursively(dir); //creates dir1\dir2\dir3 in C:\temp
2

You can use the next function

const recursiveUpload = (path: string) => { const paths = path.split("/")

const fullPath = paths.reduce((accumulator, current) => {
  fs.mkdirSync(accumulator)
  return `${accumulator}/${current}`
  })

  fs.mkdirSync(fullPath)

  return fullPath
}

So what it does:

  1. Create paths variable, where it stores every path by itself as an element of the array.
  2. Adds "/" at the end of each element in the array.
  3. Makes for the cycle:
    1. Creates a directory from the concatenation of array elements which indexes are from 0 to current iteration. Basically, it is recursive.

Hope that helps!

By the way, in Node v10.12.0 you can use recursive path creation by giving it as the additional argument.

fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => { if (err) throw err; });

https://nodejs.org/api/fs.html#fs_fs_mkdirsync_path_options

hypeofpipe
  • 503
  • 4
  • 16
2

You can simply check folder exist or not in path recursively and make the folder as you check if they are not present. (NO EXTERNAL LIBRARY)

function checkAndCreateDestinationPath (fileDestination) {
    const dirPath = fileDestination.split('/');
    dirPath.forEach((element, index) => {
        if(!fs.existsSync(dirPath.slice(0, index + 1).join('/'))){
            fs.mkdirSync(dirPath.slice(0, index + 1).join('/')); 
        }
    });
}
Pulkit Aggarwal
  • 2,554
  • 4
  • 23
  • 33
1

Too many answers, but here's a solution without recursion that works by splitting the path and then left-to-right building it back up again

function mkdirRecursiveSync(path) {
    let paths = path.split(path.delimiter);
    let fullPath = '';
    paths.forEach((path) => {

        if (fullPath === '') {
            fullPath = path;
        } else {
            fullPath = fullPath + '/' + path;
        }

        if (!fs.existsSync(fullPath)) {
            fs.mkdirSync(fullPath);
        }
    });
};

For those concerned about windows vs Linux compatibility, simply replace the forward slash with double backslash '\' in both occurrence above but TBH we are talking about node fs not windows command line and the former is pretty forgiving and the above code will simply work on Windows and is more a complete solution cross platform.

Playdome.io
  • 3,137
  • 2
  • 17
  • 34
Hamiora
  • 561
  • 5
  • 5
  • files on windows are handled with backslash not forward slash. Your code simply won't work there. C:\data\test ... – Playdome.io Jun 07 '18 at 16:04
  • 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') – Hamiora Jun 08 '18 at 21:16
  • Whatever you're saying.., my down vote still stays until you learn to write better code. – Playdome.io Jun 10 '18 at 13:56
  • 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. – Hamiora Jun 11 '18 at 21:29
  • Simply put `path.delimiter` instead of forward slash instead of arguing? Or handle both in the code so then you may get upvoted. – Playdome.io Jun 13 '18 at 07:01
  • When I try this, path.delimiter is undefined. (meanwhile, '/' works on windows) – rdm Sep 27 '18 at 18:35
  • 1
    `path.sep` is coming through as either / or \\ for me. `path.delimiter` is : or ;. – Josh Slate Jan 04 '19 at 11:42
  • @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! – ZzZombo Jan 23 '19 at 12:56
  • 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. – Hamiora Jan 24 '19 at 21:01
1
const fs = require('fs');

try {
    fs.mkdirSync(path, { recursive: true });
} catch (error) {
    // this make script keep running, even when folder already exist
    console.log(error);
}
Taylor Hx
  • 2,815
  • 23
  • 36
Choco Li
  • 139
  • 2
  • 5
0

An asynchronous way to create directories recursively:

import fs from 'fs'

const mkdirRecursive = function(path, callback) {
  let controlledPaths = []
  let paths = path.split(
    '/' // Put each path in an array
  ).filter(
    p => p != '.' // Skip root path indicator (.)
  ).reduce((memo, item) => {
    // Previous item prepended to each item so we preserve realpaths
    const prevItem = memo.length > 0 ? memo.join('/').replace(/\.\//g, '')+'/' : ''
    controlledPaths.push('./'+prevItem+item)
    return [...memo, './'+prevItem+item]
  }, []).map(dir => {
    fs.mkdir(dir, err => {
      if (err && err.code != 'EEXIST') throw err
      // Delete created directory (or skipped) from controlledPath
      controlledPaths.splice(controlledPaths.indexOf(dir), 1)
      if (controlledPaths.length === 0) {
        return callback()
      }
    })
  })
}

// Usage
mkdirRecursive('./photos/recent', () => {
  console.log('Directories created succesfully!')
})
muratgozel
  • 2,333
  • 27
  • 31
0

Here's my imperative version of mkdirp for nodejs.

function mkdirSyncP(location) {
    let normalizedPath = path.normalize(location);
    let parsedPathObj = path.parse(normalizedPath);
    let curDir = parsedPathObj.root;
    let folders = parsedPathObj.dir.split(path.sep);
    folders.push(parsedPathObj.base);
    for(let part of folders) {
        curDir = path.join(curDir, part);
        if (!fs.existsSync(curDir)) {
            fs.mkdirSync(curDir);
        }
    }
}
ubershmekel
  • 11,864
  • 10
  • 72
  • 89
0

How about this approach :

if (!fs.existsSync(pathToFile)) {
            var dirName = "";
            var filePathSplit = pathToFile.split('/');
            for (var index = 0; index < filePathSplit.length; index++) {
                dirName += filePathSplit[index]+'/';
                if (!fs.existsSync(dirName))
                    fs.mkdirSync(dirName);
            }
        }

This works for relative path.

0

Based on mouneer's zero-dependencies answer, here's a slightly more beginner friendly Typescript variant, as a module:

import * as fs from 'fs';
import * as path from 'path';

/**
* Recursively creates directories until `targetDir` is valid.
* @param targetDir target directory path to be created recursively.
* @param isRelative is the provided `targetDir` a relative path?
*/
export function mkdirRecursiveSync(targetDir: string, isRelative = false) {
    const sep = path.sep;
    const initDir = path.isAbsolute(targetDir) ? sep : '';
    const baseDir = isRelative ? __dirname : '.';

    targetDir.split(sep).reduce((prevDirPath, dirToCreate) => {
        const curDirPathToCreate = path.resolve(baseDir, prevDirPath, dirToCreate);
        try {
            fs.mkdirSync(curDirPathToCreate);
        } catch (err) {
            if (err.code !== 'EEXIST') {
                throw err;
            }
            // caught EEXIST error if curDirPathToCreate already existed (not a problem for us).
        }

        return curDirPathToCreate; // becomes prevDirPath on next call to reduce
    }, initDir);
}
Paul-Sebastian Manole
  • 2,538
  • 1
  • 32
  • 33
0

As clean as this :)

function makedir(fullpath) {
  let destination_split = fullpath.replace('/', '\\').split('\\')
  let path_builder = destination_split[0]
  $.each(destination_split, function (i, path_segment) {
    if (i < 1) return true
    path_builder += '\\' + path_segment
    if (!fs.existsSync(path_builder)) {
      fs.mkdirSync(path_builder)
    }
  })
}
Bob Vandevliet
  • 213
  • 3
  • 14
0

I had issues with the recursive option of fs.mkdir so I made a function that does the following:

  1. Creates a list of all directories, starting with the final target dir and working up to the root parent.
  2. Creates a new list of needed directories for the mkdir function to work
  3. Makes each directory needed, including the final

    function createDirectoryIfNotExistsRecursive(dirname) {
        return new Promise((resolve, reject) => {
           const fs = require('fs');
    
           var slash = '/';
    
           // backward slashes for windows
           if(require('os').platform() === 'win32') {
              slash = '\\';
           }
           // initialize directories with final directory
           var directories_backwards = [dirname];
           var minimize_dir = dirname;
           while (minimize_dir = minimize_dir.substring(0, minimize_dir.lastIndexOf(slash))) {
              directories_backwards.push(minimize_dir);
           }
    
           var directories_needed = [];
    
           //stop on first directory found
           for(const d in directories_backwards) {
              if(!(fs.existsSync(directories_backwards[d]))) {
                 directories_needed.push(directories_backwards[d]);
              } else {
                 break;
              }
           }
    
           //no directories missing
           if(!directories_needed.length) {
              return resolve();
           }
    
           // make all directories in ascending order
           var directories_forwards = directories_needed.reverse();
    
           for(const d in directories_forwards) {
              fs.mkdirSync(directories_forwards[d]);
           }
    
           return resolve();
        });
     }
    
egekhter
  • 2,067
  • 2
  • 23
  • 32
0

I solved the problem this way - similar to other recursive answers but to me this is much easier to understand and read.

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

function mkdirRecurse(inputPath) {
  if (fs.existsSync(inputPath)) {
    return;
  }
  const basePath = path.dirname(inputPath);
  if (fs.existsSync(basePath)) {
    fs.mkdirSync(inputPath);
  }
  mkdirRecurse(basePath);
}
cyborg
  • 5,638
  • 1
  • 19
  • 25
  • 2
    if i pass in a path `/does/not/exist` it only creates the first level `does` folder ;-( – bkwdesign Dec 11 '20 at 14:39
  • Ok, I don't know why but I would have thought that would be extremely easy to see why in your case in debugging. – cyborg Dec 12 '20 at 18:29
0

Could not find an example to create dir's with the required permissions.

Create Directories async recursively with the permissions you want.

Heres a plain nodejs solution

node v18.12.1 Ubuntu 18

//-----------------------------
const fs = require('fs');
const fsPromises = fs.promises;
const checkDirAccess = async (userDir) => {
    try {
      await fsPromises.access(userDir, fs.constants.R_OK | fs.constants.W_OK);
      console.log(` ${userDir} Dir existss`);
      return userDir;
    } catch (err) {
        if(err.errno = -2)
            return await crDir(userDir);
        else
            throw err;
    }
}
const crDir = async (userDir) => {
    try {
      let newDir = await fsPromises.mkdir(userDir, { recursive: true, mode: 0o700}); 
      // When userDir is created; newDir = undefined;
      console.log(` Created new Dir ${newDir}`);
      return newDir;
    } catch (err) {
      throw err;
    }
}
const directoryPath =  ['uploads/xvc/xvc/xvc/specs', 'uploads/testDir11', 'uploads/xsXa/', 'uploads//xsb//', 'uploads//xsV/'];

const findDir = async() => {
try {
    for (const iterator of directoryPath) {
        let dirOK = await checkDirAccess(iterator);
        if(dirOK)
           console.log(`found ${dirOK}`)        
    }
    
} catch (error) {
    console.error('Error : ', error);
}
}
Ritin
  • 401
  • 6
  • 15
-1

Exec can be messy on windows. There is a more "nodie" solution. Fundamentally, you have a recursive call to see if a directory exists and dive into the child (if it does exist) or create it. Here is a function that will create the children and call a function when finished:

fs = require('fs');
makedirs = function(path, func) {
 var pth = path.replace(/['\\]+/g, '/');
 var els = pth.split('/');
 var all = "";
 (function insertOne() {
   var el = els.splice(0, 1)[0];
   if (!fs.existsSync(all + el)) {
    fs.mkdirSync(all + el);
   }
   all += el + "/";
   if (els.length == 0) {
    func();
   } else {
     insertOne();
   }
   })();

}

Greg S
  • 99
  • 2
  • 5
-1

This version works better on Windows than the top answer because it understands both / and path.sep so that forward slashes work on Windows as they should. Supports absolute and relative paths (relative to the process.cwd).

/**
 * Creates a folder and if necessary, parent folders also. Returns true
 * if any folders were created. Understands both '/' and path.sep as 
 * path separators. Doesn't try to create folders that already exist,
 * which could cause a permissions error. Gracefully handles the race 
 * condition if two processes are creating a folder. Throws on error.
 * @param targetDir Name of folder to create
 */
export function mkdirSyncRecursive(targetDir) {
  if (!fs.existsSync(targetDir)) {
    for (var i = targetDir.length-2; i >= 0; i--) {
      if (targetDir.charAt(i) == '/' || targetDir.charAt(i) == path.sep) {
        mkdirSyncRecursive(targetDir.slice(0, i));
        break;
      }
    }
    try {
      fs.mkdirSync(targetDir);
      return true;
    } catch (err) {
      if (err.code !== 'EEXIST') throw err;
    }
  }
  return false;
}
Qwertie
  • 16,354
  • 20
  • 105
  • 148