CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-multer

Node.js middleware for handling multipart/form-data, primarily used for file uploads

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

storage-engines.mddocs/

Storage Engines

Storage engines determine where and how uploaded files are stored. Multer provides built-in disk and memory storage engines, with support for custom storage implementations.

Capabilities

Disk Storage

Stores uploaded files on the local file system with configurable destination and filename handling.

/**
 * Creates a disk storage engine for saving files to the file system
 * @param options - Configuration options for disk storage
 * @returns DiskStorage instance
 */
const diskStorage = multer.diskStorage(options: DiskStorageOptions);

interface DiskStorageOptions {
  /** Directory or function to determine where files are stored */
  destination?: string | DestinationFunction;
  /** Function to determine the filename for stored files */
  filename?: FilenameFunction;
}

type DestinationFunction = (
  req: Request,
  file: File,
  cb: (error: Error | null, destination: string) => void
) => void;

type FilenameFunction = (
  req: Request,
  file: File,
  cb: (error: Error | null, filename: string) => void
) => void;

Usage Examples:

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

// Simple disk storage with static destination
const storage = multer.diskStorage({
  destination: './uploads',
  filename: (req, file, cb) => {
    // Generate unique filename with timestamp
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
    cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
  }
});

// Dynamic destination based on file type
const dynamicStorage = multer.diskStorage({
  destination: (req, file, cb) => {
    let folder = 'uploads/';
    if (file.mimetype.startsWith('image/')) {
      folder += 'images/';
    } else if (file.mimetype === 'application/pdf') {
      folder += 'documents/';
    } else {
      folder += 'others/';
    }
    cb(null, folder);
  },
  filename: (req, file, cb) => {
    // Keep original filename with timestamp prefix
    cb(null, Date.now() + '-' + file.originalname);
  }
});

// User-specific uploads
const userStorage = multer.diskStorage({
  destination: (req, file, cb) => {
    const userId = req.user ? req.user.id : 'anonymous';
    cb(null, `uploads/users/${userId}/`);
  },
  filename: (req, file, cb) => {
    // Sanitize filename and add timestamp
    const sanitized = file.originalname.replace(/[^a-zA-Z0-9.-]/g, '_');
    cb(null, Date.now() + '-' + sanitized);
  }
});

const upload = multer({ storage: storage });

Memory Storage

Stores uploaded files in memory as Buffer objects, useful for temporary processing or cloud storage uploads.

/**
 * Creates a memory storage engine for keeping files in memory
 * @returns MemoryStorage instance
 */
const memoryStorage = multer.memoryStorage();

Usage Examples:

const multer = require('multer');
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });

app.post('/process-image', upload.single('image'), (req, res) => {
  if (req.file) {
    // File is available as Buffer in req.file.buffer
    console.log('File size:', req.file.buffer.length);
    console.log('File type:', req.file.mimetype);
    
    // Process the buffer (resize, compress, etc.)
    processImageBuffer(req.file.buffer)
      .then(processedBuffer => {
        // Upload to cloud storage, save to database, etc.
        return uploadToCloud(processedBuffer, req.file.originalname);
      })
      .then(cloudUrl => {
        res.json({ success: true, url: cloudUrl });
      })
      .catch(error => {
        res.status(500).json({ error: error.message });
      });
  } else {
    res.status(400).json({ error: 'No file uploaded' });
  }
});

// Batch processing multiple files in memory
app.post('/batch-process', upload.array('files', 10), (req, res) => {
  if (req.files && req.files.length > 0) {
    const processPromises = req.files.map(file => {
      return processFileBuffer(file.buffer, file.mimetype);
    });
    
    Promise.all(processPromises)
      .then(results => {
        res.json({ processed: results.length, results });
      })
      .catch(error => {
        res.status(500).json({ error: error.message });
      });
  }
});

File Objects by Storage Type

Different storage engines add different properties to the file object:

// DiskStorage file object
interface DiskStorageFile extends File {
  /** Directory where the file was saved */
  destination: string;
  /** Name of the file within the destination directory */
  filename: string;
  /** Full path to the saved file */
  path: string;
  /** Size of the file in bytes */
  size: number;
}

// MemoryStorage file object
interface MemoryStorageFile extends File {
  /** Buffer containing the entire file */
  buffer: Buffer;
  /** Size of the file in bytes */
  size: number;
}

// Base file properties (available in both storage types)
interface File {
  /** Field name from the form */
  fieldname: string;
  /** Original filename from user's computer */
  originalname: string;
  /** File encoding type */
  encoding: string;
  /** MIME type of the file */
  mimetype: string;
}

Custom Storage Engines

You can create custom storage engines by implementing the required interface:

/**
 * Custom storage engine interface
 * Must implement _handleFile and _removeFile methods
 */
interface StorageEngine {
  _handleFile(req: Request, file: File, cb: StorageCallback): void;
  _removeFile(req: Request, file: File, cb: RemoveCallback): void;
}

type StorageCallback = (error: Error | null, info?: any) => void;
type RemoveCallback = (error: Error | null) => void;

Custom Storage Example:

// Example: Custom storage that saves to a database
function DatabaseStorage(options) {
  this.database = options.database;
}

DatabaseStorage.prototype._handleFile = function(req, file, cb) {
  const chunks = [];
  
  file.stream.on('data', chunk => chunks.push(chunk));
  file.stream.on('error', cb);
  file.stream.on('end', () => {
    const buffer = Buffer.concat(chunks);
    
    // Save to database
    this.database.saveFile({
      filename: file.originalname,
      mimetype: file.mimetype,
      data: buffer,
      size: buffer.length
    })
    .then(result => {
      cb(null, {
        fileId: result.id,
        size: buffer.length,
        filename: file.originalname
      });
    })
    .catch(cb);
  });
};

DatabaseStorage.prototype._removeFile = function(req, file, cb) {
  this.database.deleteFile(file.fileId)
    .then(() => cb(null))
    .catch(cb);
};

// Usage
const databaseStorage = new DatabaseStorage({ database: myDb });
const upload = multer({ storage: databaseStorage });

Storage Configuration Best Practices

Security Considerations

// Secure filename generation
const secureFilename = (req, file, cb) => {
  // Remove potentially dangerous characters
  const sanitized = file.originalname.replace(/[^a-zA-Z0-9.-]/g, '_');
  
  // Add timestamp to prevent conflicts
  const timestamp = Date.now();
  
  // Limit filename length
  const maxLength = 100;
  const truncated = sanitized.length > maxLength 
    ? sanitized.substring(0, maxLength) 
    : sanitized;
    
  cb(null, `${timestamp}-${truncated}`);
};

// Secure destination handling
const secureDestination = (req, file, cb) => {
  // Prevent directory traversal
  const userId = req.user?.id?.replace(/[^a-zA-Z0-9]/g, '') || 'anonymous';
  const safeDestination = path.join('uploads', userId);
  
  // Ensure directory exists
  fs.makedirs(safeDestination, { recursive: true }, (err) => {
    if (err) return cb(err);
    cb(null, safeDestination);
  });
};

Performance Considerations

// For high-volume uploads, use disk storage
const highVolumeStorage = multer.diskStorage({
  destination: './uploads',
  filename: (req, file, cb) => {
    // Use crypto for truly unique filenames
    const crypto = require('crypto');
    const hash = crypto.randomBytes(16).toString('hex');
    cb(null, hash + path.extname(file.originalname));
  }
});

// For temporary processing, use memory storage with limits
const tempProcessingUpload = multer({
  storage: multer.memoryStorage(),
  limits: {
    fileSize: 10 * 1024 * 1024, // 10MB limit
    files: 1 // Single file only
  }
});

docs

core-middleware.md

error-handling.md

index.md

storage-engines.md

tile.json