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

error-handling.mddocs/

Error Handling

Comprehensive error handling system for upload failures, validation errors, and limit violations with custom error types and detailed error reporting.

Capabilities

MulterError Class

Custom error class for Multer-specific errors with standardized error codes and optional field information.

/**
 * Custom error class for Multer-specific upload errors
 * @param code - Error code indicating the type of error
 * @param field - Optional field name where the error occurred
 */
class MulterError extends Error {
  constructor(code: string, field?: string);
  /** Error code indicating the specific type of error */
  code: string;
  /** Field name where the error occurred (if applicable) */
  field?: string;
  /** Human-readable error message */
  message: string;
  /** Error class name */
  name: string;
}

Error Codes

Standard error codes for different types of upload failures:

/** Too many parts in the multipart form */
const LIMIT_PART_COUNT = 'LIMIT_PART_COUNT';

/** File size exceeds the specified limit */
const LIMIT_FILE_SIZE = 'LIMIT_FILE_SIZE';

/** Too many files uploaded */
const LIMIT_FILE_COUNT = 'LIMIT_FILE_COUNT';

/** Field name is too long */
const LIMIT_FIELD_KEY = 'LIMIT_FIELD_KEY';

/** Field value is too long */
const LIMIT_FIELD_VALUE = 'LIMIT_FIELD_VALUE';

/** Too many fields in the form */
const LIMIT_FIELD_COUNT = 'LIMIT_FIELD_COUNT';

/** File uploaded to unexpected field */
const LIMIT_UNEXPECTED_FILE = 'LIMIT_UNEXPECTED_FILE';

/** Field name is missing */
const MISSING_FIELD_NAME = 'MISSING_FIELD_NAME';

Error Handling Patterns

/**
 * Express error handler specifically for multer errors
 * @param err - Error object (potentially MulterError)
 * @param req - Express request object
 * @param res - Express response object
 * @param next - Express next function
 */
type MulterErrorHandler = (
  err: Error | MulterError,
  req: Request,
  res: Response,
  next: NextFunction
) => void;

Usage Examples:

const multer = require('multer');
const upload = multer({
  dest: 'uploads/',
  limits: {
    fileSize: 1024 * 1024 * 2, // 2MB limit
    files: 3 // Maximum 3 files
  }
});

// Method 1: Handle errors in middleware callback
app.post('/upload', (req, res) => {
  upload.single('file')(req, res, (err) => {
    if (err instanceof multer.MulterError) {
      // Multer-specific error occurred
      switch (err.code) {
        case 'LIMIT_FILE_SIZE':
          return res.status(400).json({
            error: 'File too large',
            message: `File size exceeds 2MB limit`,
            field: err.field
          });
        case 'LIMIT_FILE_COUNT':
          return res.status(400).json({
            error: 'Too many files',
            message: 'Maximum 3 files allowed'
          });
        case 'LIMIT_UNEXPECTED_FILE':
          return res.status(400).json({
            error: 'Unexpected file',
            message: `Unexpected file in field: ${err.field}`
          });
        default:
          return res.status(400).json({
            error: 'Upload error',
            message: err.message,
            code: err.code
          });
      }
    } else if (err) {
      // Other error (e.g., from fileFilter)
      return res.status(400).json({
        error: 'Upload failed',
        message: err.message
      });
    }
    
    // No error - upload successful
    res.json({
      success: true,
      file: req.file
    });
  });
});

// Method 2: Use Express error handler middleware
app.post('/upload-with-middleware', upload.single('file'), (req, res) => {
  res.json({
    success: true,
    file: req.file
  });
});

// Global error handler for multer errors
app.use((err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    const errorResponses = {
      'LIMIT_FILE_SIZE': {
        status: 413,
        message: 'File too large'
      },
      'LIMIT_FILE_COUNT': {
        status: 400,
        message: 'Too many files'
      },
      'LIMIT_FIELD_COUNT': {
        status: 400,
        message: 'Too many fields'
      },
      'LIMIT_FIELD_KEY': {
        status: 400,
        message: 'Field name too long'
      },
      'LIMIT_FIELD_VALUE': {
        status: 400,
        message: 'Field value too long'
      },
      'LIMIT_PART_COUNT': {
        status: 400,
        message: 'Too many parts'
      },
      'LIMIT_UNEXPECTED_FILE': {
        status: 400,
        message: `Unexpected file in field: ${err.field}`
      },
      'MISSING_FIELD_NAME': {
        status: 400,
        message: 'Field name missing'
      }
    };
    
    const errorResponse = errorResponses[err.code] || {
      status: 400,
      message: 'Upload error'
    };
    
    return res.status(errorResponse.status).json({
      error: err.code,
      message: errorResponse.message,
      field: err.field
    });
  }
  
  // Handle other errors
  next(err);
});

File Filter Errors

File filter functions can throw errors to reject files:

const imageFilter = (req, file, cb) => {
  if (file.mimetype.startsWith('image/')) {
    cb(null, true);
  } else {
    // Throw error to reject file
    cb(new Error('Only image files are allowed'), false);
  }
};

const upload = multer({
  dest: 'uploads/',
  fileFilter: imageFilter
});

app.post('/upload-image', upload.single('image'), (req, res) => {
  // This will be handled by error middleware if file filter rejects
  res.json({ success: true, file: req.file });
});

Storage Engine Errors

Storage engines can also generate errors during file handling:

const problematicStorage = multer.diskStorage({
  destination: (req, file, cb) => {
    // Check if directory is writable
    const dest = './uploads';
    fs.access(dest, fs.constants.W_OK, (err) => {
      if (err) {
        cb(new Error('Upload directory not writable'), null);
      } else {
        cb(null, dest);
      }
    });
  },
  filename: (req, file, cb) => {
    // Check for duplicate filenames
    const filename = file.originalname;
    const fullPath = path.join('./uploads', filename);
    
    fs.access(fullPath, fs.constants.F_OK, (err) => {
      if (!err) {
        // File exists - generate unique name
        const timestamp = Date.now();
        const ext = path.extname(filename);
        const base = path.basename(filename, ext);
        cb(null, `${base}-${timestamp}${ext}`);
      } else {
        cb(null, filename);
      }
    });
  }
});

Advanced Error Handling

// Comprehensive error handling with logging
const createUploadHandler = (uploadConfig) => {
  const upload = multer(uploadConfig);
  
  return (req, res, next) => {
    upload.single('file')(req, res, (err) => {
      if (err) {
        // Log error for debugging
        console.error('Upload error:', {
          error: err.message,
          code: err.code,
          field: err.field,
          user: req.user?.id,
          timestamp: new Date().toISOString(),
          ip: req.ip,
          userAgent: req.headers['user-agent']
        });
        
        // Send appropriate response
        if (err instanceof multer.MulterError) {
          return res.status(400).json({
            success: false,
            error: err.code,
            message: getErrorMessage(err.code),
            field: err.field
          });
        } else {
          return res.status(500).json({
            success: false,
            error: 'UPLOAD_FAILED',
            message: 'File upload failed'
          });
        }
      }
      
      next();
    });
  };
};

const getErrorMessage = (code) => {
  const messages = {
    'LIMIT_FILE_SIZE': 'File size exceeds maximum allowed size',
    'LIMIT_FILE_COUNT': 'Too many files uploaded',
    'LIMIT_FIELD_COUNT': 'Too many form fields',
    'LIMIT_FIELD_KEY': 'Field name is too long',
    'LIMIT_FIELD_VALUE': 'Field value is too long',
    'LIMIT_PART_COUNT': 'Too many parts in multipart form',
    'LIMIT_UNEXPECTED_FILE': 'File uploaded to unexpected field',
    'MISSING_FIELD_NAME': 'Field name is required'
  };
  
  return messages[code] || 'Upload error occurred';
};

// Usage
app.post('/secure-upload', 
  createUploadHandler({
    dest: 'uploads/',
    limits: {
      fileSize: 5 * 1024 * 1024, // 5MB
      files: 1
    }
  }),
  (req, res) => {
    res.json({
      success: true,
      file: req.file
    });
  }
);

Client-Side Error Handling

For handling errors on the client side:

// Fetch API example
const uploadFile = async (file) => {
  const formData = new FormData();
  formData.append('file', file);
  
  try {
    const response = await fetch('/upload', {
      method: 'POST',
      body: formData
    });
    
    const result = await response.json();
    
    if (!response.ok) {
      // Handle different error types
      switch (result.error) {
        case 'LIMIT_FILE_SIZE':
          throw new Error('File is too large. Maximum size is 2MB.');
        case 'LIMIT_FILE_COUNT':
          throw new Error('Too many files. Maximum is 3 files.');
        case 'LIMIT_UNEXPECTED_FILE':
          throw new Error(`File uploaded to wrong field: ${result.field}`);
        default:
          throw new Error(result.message || 'Upload failed');
      }
    }
    
    return result;
  } catch (error) {
    console.error('Upload error:', error.message);
    throw error;
  }
};

// Usage in form handler
document.getElementById('uploadForm').onsubmit = async (e) => {
  e.preventDefault();
  const fileInput = document.getElementById('fileInput');
  
  if (fileInput.files.length === 0) {
    alert('Please select a file');
    return;
  }
  
  try {
    const result = await uploadFile(fileInput.files[0]);
    alert('Upload successful!');
  } catch (error) {
    alert(`Upload failed: ${error.message}`);
  }
};

docs

core-middleware.md

error-handling.md

index.md

storage-engines.md

tile.json