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!