Skip to main content

Command Palette

Search for a command to run...

Handling File Uploads in Express with Multer

Published
6 min read
Handling File Uploads in Express with Multer

Users need to upload files: profile pictures, documents, resumes, attachments. Handling this in Express requires special handling. A library called Multer makes it straightforward.

Why File Uploads Need Special Handling

Regular form data is text. But files are binary. When uploading files, the request format changes to multipart/form-data, which handles both text fields and binary files.

Express doesn't automatically parse this format. That's where Multer comes in.

What is Multer?

Multer is middleware for Express that handles file uploads. It parses multipart form data, extracts files, and saves them to disk or memory.

Install it:

npm install multer

Basic File Upload

Set up Multer to handle a single file:

import express from 'express';
import multer from 'multer';

const app = express();
const upload = multer({ dest: 'uploads/' });

app.post('/upload', upload.single('file'), (req, res) => {
  if (!req.file) {
    return res.status(400).send('No file uploaded');
  }
  res.send(`File ${req.file.filename} uploaded`);
});

app.listen(3000);
  • multer({ dest: 'uploads/' }) saves files to the uploads/ folder

  • upload.single('file') handles one file from a form field named "file"

  • req.file contains file information

A user posts a file to /upload, and Multer saves it to uploads/.

Accessing File Information

After upload, req.file contains:

{
  fieldname: 'file',
  originalname: 'photo.jpg',
  encoding: '7bit',
  mimetype: 'image/jpeg',
  destination: 'uploads/',
  filename: 'file-1234567890',
  path: 'uploads/file-1234567890',
  size: 154321
}

Use this information to track what was uploaded:

app.post('/upload', upload.single('file'), (req, res) => {
  if (!req.file) {
    return res.status(400).send('No file uploaded');
  }

  res.json({
    message: 'File uploaded successfully',
    filename: req.file.filename,
    originalName: req.file.originalname,
    size: req.file.size,
    path: req.file.path
  });
});

Multiple File Uploads

Handle multiple files with upload.array():

import express from 'express';
import multer from 'multer';

const app = express();
const upload = multer({ dest: 'uploads/' });

app.post('/upload-multiple', upload.array('files'), (req, res) => {
  if (!req.files || req.files.length === 0) {
    return res.status(400).send('No files uploaded');
  }

  const fileInfo = req.files.map(file => ({
    filename: file.filename,
    originalName: file.originalname,
    size: file.size
  }));

  res.json({ files: fileInfo });
});

app.listen(3000);

req.files is an array of uploaded files.

You can limit the number of files:

upload.array('files', 5) // Max 5 files

Customizing File Names

By default, Multer generates random filenames. Configure storage to control this:

import express from 'express';
import multer from 'multer';
import path from 'path';

const app = express();

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');
  },
  filename: (req, file, cb) => {
    // Keep original extension
    const ext = path.extname(file.originalname);
    const name = path.basename(file.originalname, ext);
    const uniqueName = name + '-' + Date.now() + ext;
    cb(null, uniqueName);
  }
});

const upload = multer({ storage });

app.post('/upload', upload.single('file'), (req, res) => {
  res.json({ filename: req.file.filename });
});

app.listen(3000);

Now files are named: photo-1234567890.jpg instead of random names.

Filtering File Types

Validate file types to prevent unwanted uploads:

import express from 'express';
import multer from 'multer';

const app = express();

const storage = multer.diskStorage({
  destination: 'uploads/',
  filename: (req, file, cb) => {
    cb(null, Date.now() + '-' + file.originalname);
  }
});

const fileFilter = (req, file, cb) => {
  // Allow only images
  const allowedMimes = ['image/jpeg', 'image/png', 'image/gif'];
  
  if (allowedMimes.includes(file.mimetype)) {
    cb(null, true);
  } else {
    cb(new Error('Only image files are allowed'));
  }
};

const upload = multer({ storage, fileFilter });

app.post('/upload-image', upload.single('image'), (req, res) => {
  res.json({ message: 'Image uploaded', filename: req.file.filename });
});

app.listen(3000);

The fileFilter function checks the MIME type and rejects non-image files.

Limiting File Size

Prevent huge files from being uploaded:

const upload = multer({
  dest: 'uploads/',
  limits: {
    fileSize: 5 * 1024 * 1024 // 5MB
  }
});

app.post('/upload', upload.single('file'), (req, res) => {
  res.json({ message: 'File uploaded' });
});

Files larger than 5MB are rejected.

Handling Multiple File Fields

Upload different types of files in one request:

const app = express();

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

const fileUpload = upload.fields([
  { name: 'profilePic', maxCount: 1 },
  { name: 'coverPhoto', maxCount: 1 },
  { name: 'documents', maxCount: 5 }
]);

app.post('/upload-profile', fileUpload, (req, res) => {
  const profilePic = req.files.profilePic?.[0];
  const coverPhoto = req.files.coverPhoto?.[0];
  const documents = req.files.documents;

  res.json({
    profilePic: profilePic?.filename,
    coverPhoto: coverPhoto?.filename,
    documentCount: documents?.length || 0
  });
});

app.listen(3000);

req.files contains objects for each field name.

Complete Realistic Example

Here's a complete user profile upload system:

import express from 'express';
import multer from 'multer';
import path from 'path';
import { fileURLToPath } from 'url';

const app = express();
const __dirname = path.dirname(fileURLToPath(import.meta.url));

app.use(express.json());

// Configure storage
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    const uploadDir = 'uploads/profiles';
    cb(null, uploadDir);
  },
  filename: (req, file, cb) => {
    const ext = path.extname(file.originalname);
    const name = 'profile-' + Date.now() + ext;
    cb(null, name);
  }
});

// Filter to allow only images
const fileFilter = (req, file, cb) => {
  const allowedMimes = ['image/jpeg', 'image/png'];
  if (allowedMimes.includes(file.mimetype)) {
    cb(null, true);
  } else {
    cb(new Error('Only JPEG and PNG images allowed'));
  }
};

const upload = multer({
  storage,
  fileFilter,
  limits: { fileSize: 2 * 1024 * 1024 } // 2MB
});

// Serve uploaded files
app.use('/images', express.static('uploads/profiles'));

// Upload endpoint
app.post('/upload-profile', upload.single('profilePic'), (req, res) => {
  if (!req.file) {
    return res.status(400).json({ error: 'No file uploaded' });
  }

  const imageUrl = `/images/${req.file.filename}`;

  res.json({
    message: 'Profile picture uploaded',
    filename: req.file.filename,
    url: imageUrl,
    size: req.file.size
  });
});

// Error handler for Multer
app.use((err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    if (err.code === 'FILE_TOO_LARGE') {
      return res.status(400).json({ error: 'File too large' });
    }
    return res.status(400).json({ error: err.message });
  }
  if (err) {
    return res.status(400).json({ error: err.message });
  }
  next();
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Memory Storage

Store files in memory instead of disk (useful for processing before saving):

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

app.post('/process-file', upload.single('file'), (req, res) => {
  // File is in req.file.buffer (as a Buffer)
  const content = req.file.buffer.toString();
  res.json({ message: 'File processed' });
});

Common Mistakes

Mistake 1: Not handling errors

// Wrong - crashes on file too large
app.post('/upload', upload.single('file'), (req, res) => {
  res.send('Done');
});

// Right - add error handler
app.use((err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    res.status(400).json({ error: err.message });
  } else {
    res.status(500).json({ error: 'Server error' });
  }
});

Mistake 2: Trusting original filename

// Wrong - security risk
const filename = file.originalname;

// Right - generate safe filename
const filename = Date.now() + '-' + file.originalname;

Mistake 3: Not validating file type

Always validate using MIME type or file extension, preferably both.

Key Takeaways

  • Multer is middleware for handling file uploads in Express

  • Use upload.single() for one file, upload.array() for multiple

  • Configure storage to control where and how files are saved

  • Use fileFilter to validate file types

  • Set size limits to prevent huge uploads

  • Always generate unique, safe filenames

  • Use error handlers to gracefully handle upload errors

  • Serve uploaded files as static files with express.static

File uploads are common in web applications. Master Multer, and you can confidently handle user-uploaded content.