Table Of Contents
Quick Fix: Basic Recursive Directory Creation
const fs = require('fs');
const fsPromises = require('fs').promises;
const path = require('path');
// Modern async method (recommended)
async function createDirectory(dirPath) {
try {
await fsPromises.mkdir(dirPath, { recursive: true });
console.log('Directory created successfully');
} catch (error) {
console.error('Error creating directory:', error);
throw error;
}
}
// With custom permissions
async function createDirectoryWithPermissions(dirPath, mode = 0o755) {
try {
await fsPromises.mkdir(dirPath, {
recursive: true,
mode: mode
});
console.log('Directory created with permissions');
} catch (error) {
console.error('Error creating directory:', error);
throw error;
}
}
// Synchronous method (blocking)
function createDirectorySync(dirPath, mode = 0o755) {
try {
fs.mkdirSync(dirPath, {
recursive: true,
mode: mode
});
console.log('Directory created synchronously');
} catch (error) {
console.error('Error creating directory:', error);
throw error;
}
}
// Check if directory was created
async function createAndVerify(dirPath) {
try {
await fsPromises.mkdir(dirPath, { recursive: true });
// Verify creation
const stats = await fsPromises.stat(dirPath);
if (stats.isDirectory()) {
console.log('Directory created and verified');
return true;
}
throw new Error('Path exists but is not a directory');
} catch (error) {
console.error('Error creating/verifying directory:', error);
return false;
}
}
// Usage examples
async function examples() {
// Simple directory creation
await createDirectory('./logs/app/2025');
// With specific permissions
await createDirectoryWithPermissions('./uploads/images/thumbnails', 0o755);
// Synchronous creation
createDirectorySync('./temp/cache/files');
// Create and verify
const success = await createAndVerify('./data/processed/daily');
console.log('Creation verified:', success);
}
The Problem: Production Directory Management System
const fs = require('fs');
const fsPromises = require('fs').promises;
const path = require('path');
const os = require('os');
// Advanced directory management service
class DirectoryManager {
constructor(options = {}) {
this.defaultMode = options.defaultMode || 0o755;
this.createParents = options.createParents !== false;
this.overwriteFiles = options.overwriteFiles === true;
this.dryRun = options.dryRun === true;
this.enableLogging = options.enableLogging !== false;
this.maxDepth = options.maxDepth || 50; // Prevent infinite recursion
this.enableBackup = options.enableBackup === true;
this.backupSuffix = options.backupSuffix || '.backup';
this.tempPrefix = options.tempPrefix || 'dir-create-';
}
// Create directory with comprehensive options
async createDirectory(dirPath, options = {}) {
const {
mode = this.defaultMode,
recursive = true,
force = false,
backup = this.enableBackup,
atomic = false,
validate = true
} = options;
try {
const absolutePath = path.resolve(dirPath);
// Validate input
if (validate) {
this.validateDirectoryPath(absolutePath);
}
// Check depth to prevent infinite recursion
const depth = absolutePath.split(path.sep).length;
if (depth > this.maxDepth) {
throw new Error(`Directory depth exceeds maximum allowed (${this.maxDepth})`);
}
// Log operation if enabled
if (this.enableLogging) {
console.log(`Creating directory: ${absolutePath}`);
}
// Handle dry run
if (this.dryRun) {
return this.simulateDirectoryCreation(absolutePath, options);
}
// Check if path already exists
const existsResult = await this.checkExistence(absolutePath);
if (existsResult.exists) {
return this.handleExistingPath(absolutePath, existsResult, {
force,
backup,
mode
});
}
// Create directory
let finalPath = absolutePath;
if (atomic) {
finalPath = await this.atomicDirectoryCreation(absolutePath, {
mode,
recursive
});
} else {
await fsPromises.mkdir(absolutePath, {
recursive,
mode
});
}
// Verify creation and set final permissions
await this.verifyAndSetPermissions(finalPath, mode);
return {
success: true,
path: finalPath,
created: true,
mode: mode,
recursive: recursive,
timestamp: new Date()
};
} catch (error) {
return this.handleCreationError(error, dirPath);
}
}
// Atomic directory creation using temporary directory
async atomicDirectoryCreation(targetPath, options) {
const { mode, recursive } = options;
const parentDir = path.dirname(targetPath);
const dirName = path.basename(targetPath);
const tempName = this.tempPrefix + dirName + '-' + Date.now();
const tempPath = path.join(parentDir, tempName);
try {
// Ensure parent directory exists
if (recursive) {
await fsPromises.mkdir(parentDir, { recursive: true, mode });
}
// Create with temporary name
await fsPromises.mkdir(tempPath, { mode });
// Atomic rename to final name
await fsPromises.rename(tempPath, targetPath);
return targetPath;
} catch (error) {
// Clean up temp directory on failure
try {
await fsPromises.rmdir(tempPath);
} catch (cleanupError) {
// Ignore cleanup errors
}
throw error;
}
}
// Create multiple directories concurrently
async createMultiple(dirPaths, options = {}) {
const {
concurrency = 10,
continueOnError = true,
sharedOptions = {}
} = options;
const results = [];
const errors = [];
// Process directories in batches
for (let i = 0; i < dirPaths.length; i += concurrency) {
const batch = dirPaths.slice(i, i + concurrency);
const batchPromises = batch.map(async (dirPath, index) => {
try {
const result = await this.createDirectory(dirPath, sharedOptions);
return { index: i + index, path: dirPath, ...result };
} catch (error) {
const errorResult = {
index: i + index,
path: dirPath,
success: false,
error: error.message
};
if (continueOnError) {
return errorResult;
} else {
throw errorResult;
}
}
});
const batchResults = await Promise.allSettled(batchPromises);
batchResults.forEach(promiseResult => {
if (promiseResult.status === 'fulfilled') {
const result = promiseResult.value;
if (result.error) {
errors.push(result);
} else {
results.push(result);
}
} else {
errors.push({
index: -1,
path: 'unknown',
success: false,
error: promiseResult.reason
});
}
});
}
return {
success: errors.length === 0 || continueOnError,
results: results.sort((a, b) => a.index - b.index),
errors,
total: dirPaths.length,
successCount: results.length,
errorCount: errors.length
};
}
// Create directory tree from structure object
async createTree(structure, basePath = '.', options = {}) {
const results = [];
for (const [name, value] of Object.entries(structure)) {
const currentPath = path.join(basePath, name);
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
// Create directory and recurse
const dirResult = await this.createDirectory(currentPath, options);
results.push(dirResult);
if (dirResult.success) {
const subResults = await this.createTree(value, currentPath, options);
results.push(...subResults);
}
} else {
// Create directory (leaf node)
const dirResult = await this.createDirectory(currentPath, options);
results.push(dirResult);
}
}
return results;
}
// Create temporary directory
async createTempDirectory(options = {}) {
const {
prefix = 'tmp-',
suffix = '',
baseDir = os.tmpdir(),
mode = this.defaultMode,
cleanup = true
} = options;
try {
const tempName = prefix + Date.now() + Math.random().toString(36).substr(2, 9) + suffix;
const tempPath = path.join(baseDir, tempName);
await fsPromises.mkdir(tempPath, { mode });
const result = {
success: true,
path: tempPath,
cleanup: cleanup ? () => this.removeDirectory(tempPath) : null,
created: new Date()
};
// Auto-cleanup on process exit if enabled
if (cleanup) {
process.on('exit', () => {
try {
fs.rmSync(tempPath, { recursive: true, force: true });
} catch (error) {
// Ignore cleanup errors on exit
}
});
}
return result;
} catch (error) {
return {
success: false,
error: error.message,
code: error.code
};
}
}
// Create directory with template structure
async createFromTemplate(templatePath, targetPath, options = {}) {
const { variables = {}, mode = this.defaultMode } = options;
try {
// Read template structure
const template = await this.readTemplate(templatePath);
// Process template with variables
const processedStructure = this.processTemplate(template, variables);
// Create target directory
await this.createDirectory(targetPath, { mode });
// Create structure from template
const results = await this.createTree(processedStructure, targetPath, { mode });
return {
success: true,
path: targetPath,
template: templatePath,
created: results.filter(r => r.success).length,
failed: results.filter(r => !r.success).length,
results
};
} catch (error) {
return {
success: false,
error: error.message,
template: templatePath,
target: targetPath
};
}
}
// Synchronous directory creation with retries
createDirectorySync(dirPath, options = {}) {
const {
mode = this.defaultMode,
recursive = true,
maxRetries = 3,
retryDelay = 100
} = options;
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const absolutePath = path.resolve(dirPath);
fs.mkdirSync(absolutePath, {
recursive,
mode
});
return {
success: true,
path: absolutePath,
created: true,
attempts: attempt + 1
};
} catch (error) {
lastError = error;
if (attempt < maxRetries && this.isRetryableError(error)) {
// Wait before retry
const delay = retryDelay * Math.pow(2, attempt);
this.sleepSync(delay);
continue;
}
break;
}
}
return {
success: false,
error: lastError.message,
code: lastError.code,
attempts: maxRetries + 1
};
}
// Helper methods
async checkExistence(dirPath) {
try {
const stats = await fsPromises.stat(dirPath);
return {
exists: true,
isDirectory: stats.isDirectory(),
isFile: stats.isFile(),
permissions: stats.mode,
size: stats.size,
modified: stats.mtime
};
} catch (error) {
if (error.code === 'ENOENT') {
return { exists: false };
}
throw error;
}
}
async handleExistingPath(dirPath, existsResult, options) {
const { force, backup, mode } = options;
if (existsResult.isDirectory) {
// Directory already exists - check permissions
await this.verifyAndSetPermissions(dirPath, mode);
return {
success: true,
path: dirPath,
created: false,
existed: true,
isDirectory: true
};
}
if (existsResult.isFile) {
if (!force && !this.overwriteFiles) {
throw new Error(`Path exists as file: ${dirPath}`);
}
// Backup existing file if requested
if (backup) {
await this.createBackup(dirPath);
}
// Remove file and create directory
await fsPromises.unlink(dirPath);
await fsPromises.mkdir(dirPath, { mode });
return {
success: true,
path: dirPath,
created: true,
replacedFile: true,
backup: backup
};
}
throw new Error(`Path exists as unknown type: ${dirPath}`);
}
async verifyAndSetPermissions(dirPath, mode) {
try {
const stats = await fsPromises.stat(dirPath);
if (!stats.isDirectory()) {
throw new Error(`Path exists but is not a directory: ${dirPath}`);
}
// Set permissions if different
if ((stats.mode & 0o777) !== mode) {
await fsPromises.chmod(dirPath, mode);
}
return true;
} catch (error) {
if (error.code === 'ENOENT') {
throw new Error(`Directory was not created: ${dirPath}`);
}
throw error;
}
}
validateDirectoryPath(dirPath) {
if (!dirPath || typeof dirPath !== 'string') {
throw new Error('Directory path must be a non-empty string');
}
// Prevent path traversal
const normalizedPath = path.normalize(dirPath);
if (normalizedPath.includes('..')) {
throw new Error('Path traversal not allowed');
}
// Check for invalid characters (Windows)
if (process.platform === 'win32') {
const invalidChars = /[<>:"|?*]/;
if (invalidChars.test(dirPath)) {
throw new Error('Path contains invalid characters for Windows');
}
}
return normalizedPath;
}
simulateDirectoryCreation(dirPath, options) {
return {
success: true,
path: dirPath,
created: true,
dryRun: true,
options: options,
wouldCreate: true
};
}
handleCreationError(error, dirPath) {
let errorCode = error.code || 'UNKNOWN';
let errorMessage = error.message;
// Provide more helpful error messages
switch (error.code) {
case 'EACCES':
errorMessage = `Permission denied: Cannot create directory at ${dirPath}`;
break;
case 'EEXIST':
errorMessage = `Directory already exists: ${dirPath}`;
break;
case 'ENOTDIR':
errorMessage = `Parent path is not a directory: ${dirPath}`;
break;
case 'ENOSPC':
errorMessage = `No space left on device: ${dirPath}`;
break;
}
return {
success: false,
error: errorMessage,
code: errorCode,
path: dirPath,
timestamp: new Date()
};
}
async createBackup(filePath) {
const backupPath = filePath + this.backupSuffix;
await fsPromises.copyFile(filePath, backupPath);
return backupPath;
}
async removeDirectory(dirPath) {
try {
await fsPromises.rm(dirPath, { recursive: true, force: true });
return { success: true, path: dirPath };
} catch (error) {
return { success: false, error: error.message, path: dirPath };
}
}
isRetryableError(error) {
const retryableCodes = ['EBUSY', 'EMFILE', 'ENFILE', 'EAGAIN'];
return retryableCodes.includes(error.code);
}
sleepSync(ms) {
const start = Date.now();
while (Date.now() - start < ms) {
// Busy wait
}
}
async readTemplate(templatePath) {
// Implementation would read a template file
// For demo, return a simple structure
return {
'src': {
'components': {},
'utils': {},
'styles': {}
},
'tests': {},
'docs': {}
};
}
processTemplate(template, variables) {
// Simple template processing - replace variables
const processed = JSON.parse(JSON.stringify(template));
// Could implement more sophisticated template processing here
return processed;
}
}
// Express middleware for directory operations
function createDirectoryMiddleware(dirManager) {
return async (req, res, next) => {
try {
const { path: dirPath, mode, recursive = true } = req.body;
if (!dirPath) {
return res.status(400).json({
error: 'Directory path required',
code: 'PATH_MISSING'
});
}
const result = await dirManager.createDirectory(dirPath, {
mode: mode ? parseInt(mode, 8) : undefined,
recursive: recursive
});
res.json(result);
} catch (error) {
console.error('Directory middleware error:', error);
res.status(500).json({
error: 'Directory creation failed',
code: 'CREATION_ERROR'
});
}
};
}
// Express application
const express = require('express');
const app = express();
// Initialize directory manager
const dirManager = new DirectoryManager({
defaultMode: 0o755,
enableLogging: true,
enableBackup: false
});
app.use(express.json());
// Routes
app.post('/api/create-directory', createDirectoryMiddleware(dirManager));
app.post('/api/create-multiple', async (req, res) => {
try {
const { paths, options } = req.body;
if (!Array.isArray(paths)) {
return res.status(400).json({
error: 'Paths array required',
code: 'INVALID_INPUT'
});
}
const result = await dirManager.createMultiple(paths, options);
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.post('/api/create-temp', async (req, res) => {
try {
const result = await dirManager.createTempDirectory(req.body);
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Demonstration
async function demonstrateUsage() {
try {
// Basic directory creation
const basic = await dirManager.createDirectory('./test-dirs/basic');
console.log('Basic creation:', basic.success);
// Multiple directories
const multiple = await dirManager.createMultiple([
'./test-dirs/multi/dir1',
'./test-dirs/multi/dir2',
'./test-dirs/multi/dir3'
]);
console.log('Multiple creation:', multiple.successCount, 'of', multiple.total);
// Directory tree
const tree = await dirManager.createTree({
'project': {
'src': {
'components': {},
'utils': {}
},
'tests': {},
'docs': {}
}
}, './test-dirs');
console.log('Tree creation:', tree.filter(r => r.success).length, 'directories');
// Temporary directory
const temp = await dirManager.createTempDirectory({
prefix: 'example-',
cleanup: true
});
console.log('Temp directory:', temp.success ? temp.path : temp.error);
} catch (error) {
console.error('Demonstration error:', error);
}
}
module.exports = {
DirectoryManager,
createDirectoryMiddleware,
app
};
Recursive directory creation solves "directory structure setup", "file organization", and "deployment automation" issues. Use fs.mkdir with recursive option, handle permissions properly, implement atomic operations for safety. Support bulk creation, template-based structures, temporary directories. Handle existing paths gracefully and provide detailed error information. Alternative: shell commands, file utilities, build tools.
Share this article
Add Comment
No comments yet. Be the first to comment!