Navigation

Node.js

How to Create and Clean Up Temp Files Safely

Manage temporary files securely with automatic cleanup, collision prevention, and proper permissions in Node.js 2025

Table Of Contents

Quick Fix: Basic Temporary File Operations

const fs = require('fs');
const fsPromises = require('fs').promises;
const path = require('path');
const os = require('os');
const crypto = require('crypto');

// Create temporary file
async function createTempFile(options = {}) {
  const {
    prefix = 'tmp-',
    suffix = '',
    dir = os.tmpdir(),
    mode = 0o600 // Read/write for owner only
  } = options;

  try {
    const randomName = crypto.randomBytes(16).toString('hex');
    const filename = `${prefix}${randomName}${suffix}`;
    const tempPath = path.join(dir, filename);

    // Create the file
    await fsPromises.writeFile(tempPath, '', { mode });
    
    console.log('Temporary file created:', tempPath);
    return tempPath;
  } catch (error) {
    console.error('Error creating temp file:', error);
    throw error;
  }
}

// Create temporary directory
async function createTempDir(options = {}) {
  const {
    prefix = 'tmpdir-',
    dir = os.tmpdir(),
    mode = 0o700 // Read/write/execute for owner only
  } = options;

  try {
    const randomName = crypto.randomBytes(16).toString('hex');
    const dirName = `${prefix}${randomName}`;
    const tempPath = path.join(dir, dirName);

    await fsPromises.mkdir(tempPath, { mode });
    
    console.log('Temporary directory created:', tempPath);
    return tempPath;
  } catch (error) {
    console.error('Error creating temp directory:', error);
    throw error;
  }
}

// Clean up temporary file
async function cleanupTempFile(tempPath) {
  try {
    await fsPromises.unlink(tempPath);
    console.log('Temporary file cleaned up:', tempPath);
  } catch (error) {
    if (error.code !== 'ENOENT') {
      console.error('Error cleaning up temp file:', error);
      throw error;
    }
  }
}

// Clean up temporary directory
async function cleanupTempDir(tempPath) {
  try {
    await fsPromises.rm(tempPath, { recursive: true, force: true });
    console.log('Temporary directory cleaned up:', tempPath);
  } catch (error) {
    console.error('Error cleaning up temp directory:', error);
    throw error;
  }
}

// Auto-cleanup with process exit handlers
function withAutoCleanup(tempPath) {
  const cleanup = () => {
    try {
      fs.rmSync(tempPath, { recursive: true, force: true });
    } catch (error) {
      // Ignore cleanup errors on exit
    }
  };

  process.once('exit', cleanup);
  process.once('SIGINT', cleanup);
  process.once('SIGTERM', cleanup);
  process.once('uncaughtException', cleanup);

  return cleanup;
}

// Usage examples
async function examples() {
  // Create temporary file
  const tempFile = await createTempFile({
    prefix: 'myapp-',
    suffix: '.txt'
  });

  try {
    // Use the temporary file
    await fsPromises.writeFile(tempFile, 'Hello, temporary world!');
    const content = await fsPromises.readFile(tempFile, 'utf8');
    console.log('Temp file content:', content);
  } finally {
    // Clean up
    await cleanupTempFile(tempFile);
  }

  // Create temporary directory with auto-cleanup
  const tempDir = await createTempDir({ prefix: 'myapp-workdir-' });
  const cleanup = withAutoCleanup(tempDir);

  try {
    // Use the temporary directory
    const workFile = path.join(tempDir, 'work.txt');
    await fsPromises.writeFile(workFile, 'Working...');
  } finally {
    // Manual cleanup
    cleanup();
  }
}

The Problem: Production Temporary File Management System

const fs = require('fs');
const fsPromises = require('fs').promises;
const path = require('path');
const os = require('os');
const crypto = require('crypto');

// Advanced temporary file manager
class TempFileManager {
  constructor(options = {}) {
    this.defaultPrefix = options.defaultPrefix || 'temp-';
    this.defaultSuffix = options.defaultSuffix || '';
    this.defaultDir = options.defaultDir || os.tmpdir();
    this.defaultMode = options.defaultMode || 0o600;
    this.autoCleanup = options.autoCleanup !== false;
    this.maxAge = options.maxAge || 24 * 60 * 60 * 1000; // 24 hours
    this.maxSize = options.maxSize || 100 * 1024 * 1024; // 100MB
    this.enableTracking = options.enableTracking !== false;
    this.cleanupInterval = options.cleanupInterval || 60 * 60 * 1000; // 1 hour
    
    // Track temporary files for cleanup
    this.trackedFiles = new Map();
    this.trackedDirs = new Set();
    this.cleanupHandlers = new Set();
    
    if (this.autoCleanup) {
      this.setupCleanupHandlers();
    }
    
    if (this.enableTracking) {
      this.startPeriodicCleanup();
    }
  }

  // Create temporary file with advanced options
  async createFile(options = {}) {
    const {
      prefix = this.defaultPrefix,
      suffix = this.defaultSuffix,
      dir = this.defaultDir,
      mode = this.defaultMode,
      content = null,
      encoding = 'utf8',
      track = this.enableTracking,
      maxSize = this.maxSize,
      template = null
    } = options;

    try {
      // Ensure directory exists
      await fsPromises.mkdir(dir, { recursive: true });

      // Generate unique filename
      const tempPath = await this.generateUniquePath(dir, prefix, suffix);

      // Validate content size
      if (content && Buffer.byteLength(content, encoding) > maxSize) {
        throw new Error(`Content size exceeds maximum allowed size: ${maxSize}`);
      }

      // Create file with initial content
      const initialContent = content || template || '';
      await fsPromises.writeFile(tempPath, initialContent, { mode, encoding });

      // Track file for cleanup
      if (track) {
        this.trackFile(tempPath, {
          created: new Date(),
          mode,
          autoCleanup: true,
          maxAge: this.maxAge
        });
      }

      return {
        path: tempPath,
        cleanup: () => this.cleanup(tempPath),
        write: (data, writeOptions = {}) => this.writeToFile(tempPath, data, { encoding, ...writeOptions }),
        read: (readOptions = {}) => this.readFromFile(tempPath, { encoding, ...readOptions }),
        append: (data, appendOptions = {}) => this.appendToFile(tempPath, data, { encoding, ...appendOptions }),
        stats: () => fsPromises.stat(tempPath),
        exists: () => this.exists(tempPath)
      };
    } catch (error) {
      throw new Error(`Failed to create temporary file: ${error.message}`);
    }
  }

  // Create temporary directory with advanced options
  async createDirectory(options = {}) {
    const {
      prefix = this.defaultPrefix,
      dir = this.defaultDir,
      mode = 0o700,
      track = this.enableTracking,
      structure = null
    } = options;

    try {
      // Ensure parent directory exists
      await fsPromises.mkdir(dir, { recursive: true });

      // Generate unique directory name
      const tempPath = await this.generateUniquePath(dir, prefix, '', true);

      // Create directory
      await fsPromises.mkdir(tempPath, { mode });

      // Create directory structure if provided
      if (structure) {
        await this.createDirectoryStructure(tempPath, structure);
      }

      // Track directory for cleanup
      if (track) {
        this.trackDirectory(tempPath, {
          created: new Date(),
          mode,
          autoCleanup: true,
          maxAge: this.maxAge
        });
      }

      return {
        path: tempPath,
        cleanup: () => this.cleanup(tempPath),
        createFile: (filename, content = '', fileOptions = {}) => 
          this.createFileInDir(tempPath, filename, content, fileOptions),
        createSubDir: (dirname, dirOptions = {}) => 
          this.createSubDirectory(tempPath, dirname, dirOptions),
        list: (listOptions = {}) => this.listDirectory(tempPath, listOptions),
        exists: () => this.exists(tempPath)
      };
    } catch (error) {
      throw new Error(`Failed to create temporary directory: ${error.message}`);
    }
  }

  // Create temporary file with automatic cleanup
  async withTempFile(callback, options = {}) {
    const { autoDelete = true } = options;
    
    const tempFile = await this.createFile(options);
    
    try {
      const result = await callback(tempFile);
      return result;
    } finally {
      if (autoDelete) {
        await tempFile.cleanup();
      }
    }
  }

  // Create temporary directory with automatic cleanup
  async withTempDirectory(callback, options = {}) {
    const { autoDelete = true } = options;
    
    const tempDir = await this.createDirectory(options);
    
    try {
      const result = await callback(tempDir);
      return result;
    } finally {
      if (autoDelete) {
        await tempDir.cleanup();
      }
    }
  }

  // Generate unique temporary path
  async generateUniquePath(dir, prefix, suffix, isDirectory = false) {
    let attempts = 0;
    const maxAttempts = 10;

    while (attempts < maxAttempts) {
      const randomId = crypto.randomBytes(16).toString('hex');
      const timestamp = Date.now().toString(36);
      const uniquePart = `${timestamp}-${randomId}`;
      const filename = `${prefix}${uniquePart}${suffix}`;
      const fullPath = path.join(dir, filename);

      try {
        // Check if path already exists
        await fsPromises.access(fullPath);
        attempts++;
        continue;
      } catch {
        // Path doesn't exist, we can use it
        return fullPath;
      }
    }

    throw new Error(`Failed to generate unique temporary path after ${maxAttempts} attempts`);
  }

  // Track file for automatic cleanup
  trackFile(filePath, metadata = {}) {
    this.trackedFiles.set(filePath, {
      type: 'file',
      created: new Date(),
      accessed: new Date(),
      ...metadata
    });
  }

  // Track directory for automatic cleanup
  trackDirectory(dirPath, metadata = {}) {
    this.trackedDirs.add(dirPath);
    this.trackedFiles.set(dirPath, {
      type: 'directory',
      created: new Date(),
      accessed: new Date(),
      ...metadata
    });
  }

  // Clean up specific path
  async cleanup(targetPath) {
    try {
      const stats = await fsPromises.stat(targetPath);
      
      if (stats.isDirectory()) {
        await fsPromises.rm(targetPath, { recursive: true, force: true });
        this.trackedDirs.delete(targetPath);
      } else {
        await fsPromises.unlink(targetPath);
      }
      
      this.trackedFiles.delete(targetPath);
      
      return {
        success: true,
        path: targetPath,
        type: stats.isDirectory() ? 'directory' : 'file'
      };
    } catch (error) {
      if (error.code === 'ENOENT') {
        // File/directory already doesn't exist
        this.trackedFiles.delete(targetPath);
        this.trackedDirs.delete(targetPath);
        return {
          success: true,
          path: targetPath,
          alreadyRemoved: true
        };
      }
      
      return {
        success: false,
        path: targetPath,
        error: error.message
      };
    }
  }

  // Clean up all tracked temporary files
  async cleanupAll() {
    const results = [];
    
    for (const [filePath] of this.trackedFiles) {
      const result = await this.cleanup(filePath);
      results.push(result);
    }
    
    return {
      total: results.length,
      successful: results.filter(r => r.success).length,
      failed: results.filter(r => !r.success).length,
      results
    };
  }

  // Clean up old temporary files
  async cleanupOld(maxAge = this.maxAge) {
    const now = Date.now();
    const results = [];
    
    for (const [filePath, metadata] of this.trackedFiles) {
      if (metadata.autoCleanup && metadata.created) {
        const age = now - metadata.created.getTime();
        
        if (age > maxAge) {
          const result = await this.cleanup(filePath);
          result.reason = 'expired';
          result.age = age;
          results.push(result);
        }
      }
    }
    
    return {
      total: results.length,
      successful: results.filter(r => r.success).length,
      failed: results.filter(r => !r.success).length,
      results
    };
  }

  // File operations helpers
  async writeToFile(filePath, data, options = {}) {
    const { encoding = 'utf8', flag = 'w' } = options;
    
    try {
      await fsPromises.writeFile(filePath, data, { encoding, flag });
      
      // Update access time
      if (this.trackedFiles.has(filePath)) {
        this.trackedFiles.get(filePath).accessed = new Date();
      }
      
      return {
        success: true,
        path: filePath,
        bytesWritten: Buffer.byteLength(data, encoding)
      };
    } catch (error) {
      return {
        success: false,
        path: filePath,
        error: error.message
      };
    }
  }

  async readFromFile(filePath, options = {}) {
    const { encoding = 'utf8' } = options;
    
    try {
      const data = await fsPromises.readFile(filePath, { encoding });
      
      // Update access time
      if (this.trackedFiles.has(filePath)) {
        this.trackedFiles.get(filePath).accessed = new Date();
      }
      
      return {
        success: true,
        path: filePath,
        data,
        size: Buffer.byteLength(data, encoding)
      };
    } catch (error) {
      return {
        success: false,
        path: filePath,
        error: error.message
      };
    }
  }

  async appendToFile(filePath, data, options = {}) {
    const { encoding = 'utf8' } = options;
    
    try {
      await fsPromises.appendFile(filePath, data, { encoding });
      
      // Update access time
      if (this.trackedFiles.has(filePath)) {
        this.trackedFiles.get(filePath).accessed = new Date();
      }
      
      return {
        success: true,
        path: filePath,
        bytesAppended: Buffer.byteLength(data, encoding)
      };
    } catch (error) {
      return {
        success: false,
        path: filePath,
        error: error.message
      };
    }
  }

  // Directory operations helpers
  async createFileInDir(dirPath, filename, content = '', options = {}) {
    const filePath = path.join(dirPath, filename);
    const { mode = this.defaultMode, encoding = 'utf8' } = options;
    
    await fsPromises.writeFile(filePath, content, { mode, encoding });
    
    // Track the file
    if (this.enableTracking) {
      this.trackFile(filePath, {
        created: new Date(),
        mode,
        autoCleanup: true,
        parent: dirPath
      });
    }
    
    return filePath;
  }

  async createSubDirectory(parentPath, dirname, options = {}) {
    const dirPath = path.join(parentPath, dirname);
    const { mode = 0o700 } = options;
    
    await fsPromises.mkdir(dirPath, { mode });
    
    // Track the directory
    if (this.enableTracking) {
      this.trackDirectory(dirPath, {
        created: new Date(),
        mode,
        autoCleanup: true,
        parent: parentPath
      });
    }
    
    return dirPath;
  }

  async listDirectory(dirPath, options = {}) {
    const { withFileTypes = true } = options;
    
    try {
      const entries = await fsPromises.readdir(dirPath, { withFileTypes });
      
      return {
        success: true,
        path: dirPath,
        entries: entries.map(entry => ({
          name: entry.name,
          isFile: entry.isFile(),
          isDirectory: entry.isDirectory(),
          fullPath: path.join(dirPath, entry.name)
        }))
      };
    } catch (error) {
      return {
        success: false,
        path: dirPath,
        error: error.message
      };
    }
  }

  async createDirectoryStructure(basePath, structure) {
    for (const [name, value] of Object.entries(structure)) {
      const fullPath = path.join(basePath, name);
      
      if (typeof value === 'object' && value !== null) {
        // It's a directory
        await fsPromises.mkdir(fullPath, { mode: 0o700 });
        await this.createDirectoryStructure(fullPath, value);
      } else {
        // It's a file
        await fsPromises.writeFile(fullPath, value || '', { mode: this.defaultMode });
      }
    }
  }

  // Utility methods
  async exists(targetPath) {
    try {
      await fsPromises.access(targetPath);
      return true;
    } catch {
      return false;
    }
  }

  getTrackedCount() {
    return {
      total: this.trackedFiles.size,
      files: Array.from(this.trackedFiles.values()).filter(m => m.type === 'file').length,
      directories: Array.from(this.trackedFiles.values()).filter(m => m.type === 'directory').length
    };
  }

  // Setup automatic cleanup handlers
  setupCleanupHandlers() {
    const cleanup = () => {
      try {
        for (const [filePath] of this.trackedFiles) {
          try {
            const stats = fs.statSync(filePath);
            if (stats.isDirectory()) {
              fs.rmSync(filePath, { recursive: true, force: true });
            } else {
              fs.unlinkSync(filePath);
            }
          } catch {
            // Ignore errors during cleanup
          }
        }
      } catch {
        // Ignore cleanup errors
      }
    };

    // Add cleanup handlers
    process.once('exit', cleanup);
    process.once('SIGINT', cleanup);
    process.once('SIGTERM', cleanup);
    process.once('uncaughtException', cleanup);
    process.once('unhandledRejection', cleanup);

    this.cleanupHandlers.add(cleanup);
  }

  // Start periodic cleanup of old files
  startPeriodicCleanup() {
    this.cleanupTimer = setInterval(async () => {
      try {
        await this.cleanupOld();
      } catch (error) {
        console.warn('Periodic cleanup failed:', error.message);
      }
    }, this.cleanupInterval);

    // Clear timer on cleanup
    this.cleanupHandlers.add(() => {
      if (this.cleanupTimer) {
        clearInterval(this.cleanupTimer);
      }
    });
  }

  // Stop the manager and clean up
  async stop() {
    // Stop periodic cleanup
    if (this.cleanupTimer) {
      clearInterval(this.cleanupTimer);
    }

    // Clean up all tracked files
    const result = await this.cleanupAll();

    // Remove event handlers
    this.cleanupHandlers.forEach(handler => {
      try {
        handler();
      } catch {
        // Ignore errors
      }
    });
    this.cleanupHandlers.clear();

    return result;
  }
}

// Express middleware for temporary file operations
function createTempFileMiddleware(tempManager) {
  return async (req, res, next) => {
    req.tempManager = tempManager;
    
    req.createTempFile = async (options = {}) => {
      return await tempManager.createFile(options);
    };
    
    req.createTempDir = async (options = {}) => {
      return await tempManager.createDirectory(options);
    };
    
    req.withTempFile = async (callback, options = {}) => {
      return await tempManager.withTempFile(callback, options);
    };
    
    req.withTempDir = async (callback, options = {}) => {
      return await tempManager.withTempDirectory(callback, options);
    };
    
    next();
  };
}

// Express application
const express = require('express');
const multer = require('multer');
const app = express();

// Initialize temp file manager
const tempManager = new TempFileManager({
  defaultPrefix: 'myapp-',
  autoCleanup: true,
  enableTracking: true,
  maxAge: 2 * 60 * 60 * 1000 // 2 hours
});

// Configure multer to use temp files
const upload = multer({
  storage: multer.diskStorage({
    destination: async (req, file, cb) => {
      try {
        const tempDir = await tempManager.createDirectory({
          prefix: 'upload-'
        });
        cb(null, tempDir.path);
      } catch (error) {
        cb(error);
      }
    },
    filename: (req, file, cb) => {
      const uniqueName = crypto.randomBytes(16).toString('hex');
      const extension = path.extname(file.originalname);
      cb(null, `${uniqueName}${extension}`);
    }
  })
});

app.use(express.json());
app.use(createTempFileMiddleware(tempManager));

// Routes
app.post('/api/temp/file', async (req, res) => {
  try {
    const { content, options = {} } = req.body;
    
    const tempFile = await req.createTempFile({
      content,
      ...options
    });

    res.json({
      success: true,
      path: tempFile.path,
      operations: {
        read: '/api/temp/read',
        write: '/api/temp/write',
        cleanup: '/api/temp/cleanup'
      }
    });
  } catch (error) {
    res.status(500).json({
      error: error.message,
      code: 'TEMP_FILE_ERROR'
    });
  }
});

app.post('/api/temp/directory', async (req, res) => {
  try {
    const { structure, options = {} } = req.body;
    
    const tempDir = await req.createTempDir({
      structure,
      ...options
    });

    res.json({
      success: true,
      path: tempDir.path,
      operations: {
        list: '/api/temp/list',
        cleanup: '/api/temp/cleanup'
      }
    });
  } catch (error) {
    res.status(500).json({
      error: error.message,
      code: 'TEMP_DIR_ERROR'
    });
  }
});

app.post('/api/temp/process', upload.single('file'), async (req, res) => {
  try {
    if (!req.file) {
      return res.status(400).json({
        error: 'File required',
        code: 'FILE_MISSING'
      });
    }

    // Process file in temporary workspace
    const result = await req.withTempDir(async (tempDir) => {
      const workFile = await tempDir.createFile('processed.txt', 'Processing...');
      
      // Simulate file processing
      await new Promise(resolve => setTimeout(resolve, 1000));
      
      const processedContent = `Processed: ${req.file.originalname}\nSize: ${req.file.size} bytes\nProcessed at: ${new Date().toISOString()}`;
      
      await tempDir.createFile('result.txt', processedContent);
      
      return {
        originalFile: req.file.path,
        processed: true,
        resultPath: path.join(tempDir.path, 'result.txt')
      };
    });

    res.json({
      success: true,
      result
    });
  } catch (error) {
    res.status(500).json({
      error: error.message,
      code: 'PROCESS_ERROR'
    });
  }
});

app.get('/api/temp/stats', (req, res) => {
  const stats = tempManager.getTrackedCount();
  res.json(stats);
});

app.post('/api/temp/cleanup', async (req, res) => {
  try {
    const { path: targetPath, all = false } = req.body;
    
    let result;
    if (all) {
      result = await tempManager.cleanupAll();
    } else if (targetPath) {
      result = await tempManager.cleanup(targetPath);
    } else {
      result = await tempManager.cleanupOld();
    }

    res.json({
      success: true,
      result
    });
  } catch (error) {
    res.status(500).json({
      error: error.message,
      code: 'CLEANUP_ERROR'
    });
  }
});

// Demonstration
async function demonstrateUsage() {
  try {
    console.log('Starting temporary file management demonstration...');

    // Create temporary file with automatic cleanup
    await tempManager.withTempFile(async (tempFile) => {
      console.log('Created temp file:', tempFile.path);
      
      await tempFile.write('Hello, temporary world!');
      const content = await tempFile.read();
      console.log('Content:', content.data);
    });

    // Create temporary directory with structure
    await tempManager.withTempDirectory(async (tempDir) => {
      console.log('Created temp directory:', tempDir.path);
      
      await tempDir.createFile('config.json', JSON.stringify({ app: 'demo' }));
      await tempDir.createSubDir('logs');
      
      const listing = await tempDir.list();
      console.log('Directory contents:', listing.entries.length, 'items');
    });

    // Show tracking stats
    const stats = tempManager.getTrackedCount();
    console.log('Tracked files:', stats);

    // Clean up old files
    const cleanupResult = await tempManager.cleanupOld();
    console.log('Cleanup result:', cleanupResult.total, 'files processed');
  } catch (error) {
    console.error('Demonstration error:', error);
  }
}

// Graceful shutdown
process.on('SIGTERM', async () => {
  console.log('Shutting down, cleaning up temporary files...');
  await tempManager.stop();
  process.exit(0);
});

module.exports = {
  TempFileManager,
  createTempFileMiddleware,
  tempManager,
  app
};

Temporary file management solves "workspace isolation", "resource cleanup", and "secure file handling" issues. Create unique temporary files and directories with proper permissions, implement automatic cleanup on process exit, track resources for management. Support structured directory creation, periodic cleanup, and secure file operations. Handle concurrent access, prevent naming conflicts, and provide comprehensive error handling. Alternative: system temp utilities, containerized workspaces, cloud temporary storage.

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Node.js