Express.js File Uploads

File uploads are a fundamental requirement for modern web applications. Whether you are building a profile picture uploader, a document management system, or a content management tool, your server needs a way to receive and process binary data. Express.js itself does not handle multipart/form-data (the format used for file uploads) out of the box. To handle this, we use Multer, a powerful middleware built specifically for handling file uploads in Node.js applications.

Developer Tip: Express's built-in express.json() and express.urlencoded() parsers only handle text data. If you try to access a file through req.body without Multer, it will be undefined.

 

Key Features of File Uploads

  • Multipart Parsing: Multer does the heavy lifting of parsing incoming requests and making file data available in a req.file or req.files object.
  • Flexible Storage: You can choose to store files directly on your server's disk or keep them in memory as Buffer objects for immediate processing or cloud uploading.
  • Robust Validation: Protect your server by enforcing strict rules on file extensions, MIME types, and maximum file sizes.
  • Filename Customization: Programmatically rename files to prevent naming collisions and to keep your storage organized.

 

Setting Up File Uploads in Express.js

Install multer Middleware
First, you need to add Multer to your project dependencies using npm or yarn.

npm install multer

Basic Configuration
Before handling requests, you must configure how Multer should store incoming files. Using diskStorage gives you full control over the destination folder and the naming convention.

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

const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        // Ensure this folder exists before uploading
        cb(null, 'uploads/'); 
    },
    filename: (req, file, cb) => {
        // Create a unique filename using the current timestamp and original name
        const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
        cb(null, `${uniqueSuffix}${path.extname(file.originalname)}`);
    }
});

const upload = multer({ storage });
Common Mistake: Forgetting to manually create the 'uploads' directory. Multer will throw an error if the destination directory does not exist when it tries to save a file.

Handling Single File Upload
For a single file (like an avatar update), use the upload.single() middleware. The string passed to it must match the name attribute of the file input in your HTML form.

app.post('/upload', upload.single('avatar'), (req, res) => {
    // req.file contains information about the uploaded file
    // req.body contains text fields, if any were sent
    if (!req.file) {
        return res.status(400).send('No file uploaded.');
    }
    res.send(`File saved as: ${req.file.filename}`);
});
Watch Out: Always validate that req.file exists inside your route handler. If a user submits a form without a file, Multer won't throw an error, but req.file will be undefined.

Handling Multiple File Uploads
When users need to upload multiple documents or a gallery of images, use upload.array(). You can also set a limit on the number of files accepted.

// Accepts up to 5 files with the field name 'photos'
app.post('/upload-gallery', upload.array('photos', 5), (req, res) => {
    // req.files is an array of file objects
    const fileCount = req.files.length;
    res.send(`${fileCount} files uploaded successfully.`);
});

Validating Uploaded Files
Security is paramount when allowing file uploads. You should always restrict the file types and sizes to prevent malicious files from filling up your disk or executing code.

const uploadWithValidation = multer({
    storage,
    limits: { fileSize: 2 * 1024 * 1024 }, // 2MB limit
    fileFilter: (req, file, cb) => {
        const allowedTypes = /jpeg|jpg|png|gif/;
        const isMimeValid = allowedTypes.test(file.mimetype);
        const isExtValid = allowedTypes.test(path.extname(file.originalname).toLowerCase());

        if (isMimeValid && isExtValid) {
            return cb(null, true);
        }
        cb(new Error('Only image files (jpg, png, gif) are allowed!'));
    }
});
Best Practice: Always set a fileSize limit. Allowing unlimited file sizes makes your server vulnerable to Denial of Service (DoS) attacks where an attacker fills your storage or crashes your process with massive files.

Serving Uploaded Files
To make your uploaded files accessible via a URL (e.g., for displaying an image in a browser), use Express's static middleware.

app.use('/public/uploads', express.static('uploads'));
// A file in 'uploads/my-pic.jpg' will be accessible at 'http://localhost:3000/public/uploads/my-pic.jpg'

 

Complete Example

Here is a production-ready structure for a basic Express file upload server.

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

// 1. Configure Storage
const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        cb(null, 'uploads/');
    },
    filename: (req, file, cb) => {
        cb(null, `user-${Date.now()}${path.extname(file.originalname)}`);
    }
});

// 2. Initialize Multer
const upload = multer({ 
    storage,
    limits: { fileSize: 5 * 1024 * 1024 } // 5MB limit
});

// 3. Single File Route
app.post('/profile-pic', upload.single('avatar'), (req, res) => {
    try {
        res.status(200).json({
            message: 'Upload successful',
            file: req.file
        });
    } catch (error) {
        res.status(400).send('Error uploading file.');
    }
});

// 4. Multiple Files Route
app.post('/documents', upload.array('docs', 3), (req, res) => {
    res.send(`Received ${req.files.length} documents.`);
});

// 5. Serve Files
app.use('/uploads', express.static('uploads'));

app.listen(3000, () => {
    console.log('Server started on http://localhost:3000');
});

 

Summary

Express.js, paired with multer, provides a robust and highly configurable ecosystem for handling file uploads. By defining a storage engine, you control where and how files are saved. By implementing fileFilter and limits, you ensure your application remains secure and efficient. Remember to always sanitize filenames and validate file types to build a safe user experience.