How to Handle File Uploads in Node.js with Multer

Handling file uploads is a fundamental requirement for many modern web applications, whether you are accepting user profile pictures, documents, or media files. This article provides a straightforward, step-by-step guide on how to implement secure and efficient file uploads in a Node.js environment using the popular Multer middleware. You will learn how to install the necessary packages, configure storage options, restrict file sizes and types, and handle errors effectively in an Express application.

Prerequisites and Installation

To manage file uploads in Node.js, you need an Express application and the Multer middleware. Multer is a body-parser middleware that facilitates uploading files by handling multipart/form-data.

Initialize your project and install the required packages using npm:

npm init -y
npm install express multer

Basic Configuration and Disk Storage

To maintain control over where files are saved and how they are named, you should configure Multer’s disk storage engine. This prevents files from being saved with random names and no extensions.

Create a file named server.js and set up the basic storage configuration:

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

const app = express();
const PORT = 3000;

// Configure storage destination and filename
const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        cb(null, 'uploads/'); // Ensure this folder exists in your project root
    },
    filename: (req, file, cb) => {
        const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
        cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
    }
});

Adding File Filters and Limits

For security and performance, it is critical to limit the file size and restrict the allowed file types (such as allowing only images).

// File filter to allow only specific formats (e.g., JPEG, PNG, GIF)
const fileFilter = (req, file, cb) => {
    const allowedTypes = /jpeg|jpg|png|gif/;
    const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
    const mimetype = allowedTypes.test(file.mimetype);

    if (extname && mimetype) {
        return cb(null, true);
    } else {
        cb(new Error('Only images (jpeg, jpg, png, gif) are allowed!'));
    }
};

// Initialize Multer with storage, limits, and filter
const upload = multer({
    storage: storage,
    limits: { fileSize: 2 * 1024 * 1024 }, // Limit size to 2MB
    fileFilter: fileFilter
});

Creating the Upload Routes

With Multer configured, you can now create routes to handle both single and multiple file uploads.

Single File Upload

Use upload.single('fieldname') for a single file upload. The file details will be available in req.file.

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

Multiple Files Upload

Use upload.array('fieldname', maxCount) to handle multiple files. The file details will be available in req.files.

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

Handling Upload Errors

It is essential to handle errors gracefully, such as when a file exceeds the size limit or fails the validation filter. You can do this by using a custom Express error handler.

// Error handling middleware
app.use((err, req, res, next) => {
    if (err instanceof multer.MulterError) {
        // A Multer error occurred when uploading (e.g., file too large)
        return res.status(400).json({ error: err.message });
    } else if (err) {
        // An unknown error or custom validation error occurred
        return res.status(400).json({ error: err.message });
    }
    next();
});

app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});