Handling File Uploads in Node.js with Express and Multer

Introduction

Users don't only consume data, they also produce data and upload it. They can send data through applications like messengers or email to specific recipients or upload files to social networks and data streaming platforms such as Facebook or YouTube.

That being said, almost every interactive website today supports file uploads.

File Uploading Libraries

There are several Node libraries available on NPM that can simplify the process of validating and uploading files to server. Among them, the most popular choice these days are Multer, Formidable, and Multiparty.

They all have stable versions and are backed by an online community of open source developers.

What is Multer?

Multer is a popular Node.js middleware used for handling multipart/form-data requests. It makes use of busboy to parse any data that it received through an HTML form. This greatly enhances its performance because the busboy module is unmatched when it comes to analyzing form data.

Multer provides us control and flexibility when handling multipart/form-data requests - we get detailed information about each uploaded file, the ability to add a custom storage engine, validation of files according to our needs, the ability to set limits on uploaded files, etc.

Project Setup

Since we won't be storing our images in a database, but rather a simple folder for brevity and simplicity, let's make another folder within our project folder and name it, say, uploads.

Now, let's install Express:

$ npm i express

And finally, let's install Multer:

$ npm i multer

Project Implementation

At this point, we are ready to write some code, starting off with the HTML forms that we'll use to harvest information.

Let's start off with the form for uploading a single file:

<form method="POST" action="/upload-profile-pic" enctype="multipart/form-data">
    <div>
        <label>Select your profile picture:</label>
        <input type="file" name="profile_pic" />
    </div>
    <div>
        <input type="submit" name="btn_upload_profile_pic" value="Upload" />
    </div>
</form>

And then with a form that allows us to upload multiple files:

<form method="POST" action="/upload-multiple-images" enctype="multipart/form-data">
    <div>
        <label>Select multiple images:</label>
        <input type="file" name="multiple_images" multiple />
    </div>
    <div>
        <input type="submit" name="btn_upload_multiple_images" value="Upload" />
    </div>
</form>

You can either put these forms on separate pages or on the same one. For the purpose of this tutorial, they're one after another:

HTML5 forms for submitting files to server

The HTML forms are pretty straightforward, accepting multipart/form-data and routing to the adequate functions that handle their requests.

Express Application

With our forms ready, we can work on the actual logic for uploading and validating files through Express.

Let's create a file called app.js in the project root and start by importing the required modules:

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

Now, let's create our Express app:

const app = express();

And finally, let's set up the port on which it'll run:

const port = process.env.PORT || 3000;

The public directory off of our root folder contains the static files we want to serve, so let's set it as a static directory using express.static:

app.use(express.static(__dirname + '/public'));

At this point, let's define the storage location for our images:

const storage = multer.diskStorage({
    destination: function(req, file, cb) {
        cb(null, 'uploads/');
    },

    // By default, multer removes file extensions so let's add them back
    filename: function(req, file, cb) {
        cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname));
    }
});

And finally, let's run the app using the port we set earlier:

app.listen(port, () => console.log(`Listening on port ${port}...`));

File Validation and Upload

For fundamental security reasons, we'll want to validate files before uploading them to our servers. Let's edit the app.js file and add both functionalities:

app.post('/upload-profile-pic', (req, res) => {
    // 'profile_pic' is the name of our file input field in the HTML form
    let upload = multer({ storage: storage, fileFilter: helpers.imageFilter }).single('profile_pic');

    upload(req, res, function(err) {
        // req.file contains information of uploaded file
        // req.body contains information of text fields, if there were any

        if (req.fileValidationError) {
            return res.send(req.fileValidationError);
        }
        else if (!req.file) {
            return res.send('Please select an image to upload');
        }
        else if (err instanceof multer.MulterError) {
            return res.send(err);
        }
        else if (err) {
            return res.send(err);
        }

        // Display uploaded image for user validation
        res.send(`You have uploaded this image: <hr/><img src="${req.file.path}" width="500"><hr /><a href="./">Upload another image</a>`);
    });
});

Here, we've accepted an HTTP POST request, in which the image information is embodied. The function that actually takes care of the upload functionality is the multer().single() method.

You might have noticed the fileFilter: helpers.imageFilter but we haven't yet created/imported the helpers file. So, let's create a new file in our project directory and name it helpers.js. Here we will write some code that is used to check whether the submitted file is an image or not.

const imageFilter = function(req, file, cb) {
    // Accept images only
    if (!file.originalname.match(/\.(jpg|JPG|jpeg|JPEG|png|PNG|gif|GIF)$/)) {
        req.fileValidationError = 'Only image files are allowed!';
        return cb(new Error('Only image files are allowed!'), false);
    }
    cb(null, true);
};
exports.imageFilter = imageFilter;

Of course, to use this module, we'll have to import it at the top of our app.js file:

const helpers = require('./helpers');

Now, we can run our application and validate that it's working properly:

Uploaded single image

Uploading Multiple Files

Uploading multiple files is essentially the same as uploading a single file. Though, instead of the multer().single() function, we use the multer().array() function:

app.post('/upload-multiple-images', (req, res) => {
    // 10 is the limit I've defined for number of uploaded files at once
    // 'multiple_images' is the name of our file input field
    let upload = multer({ storage: storage, fileFilter: helpers.imageFilter }).array('multiple_images', 10);

    upload(req, res, function(err) {
        if (req.fileValidationError) {
            return res.send(req.fileValidationError);
        }
        else if (...) // The same as when uploading single images

        let result = "You have uploaded these images: <hr />";
        const files = req.files;
        let index, len;

        // Loop through all the uploaded images and display them on frontend
        for (index = 0, len = files.length; index < len; ++index) {
            result += `<img src="${files[index].path}" width="300" style="margin-right: 20px;">`;
        }
        result += '<hr/><a href="./">Upload more images</a>';
        res.send(result);
    });
});

And now, to validate if everything is working correctly:

Uploaded multiple image

Conclusion

Users don't only consume data, they produce data and, in many cases, need to upload it to a web server. They can send data through applications like messengers or email to specific recipients, or they can upload files to social networks and data streaming platforms such as Facebook or YouTube.

In this article we've used Express.js and the Multer library to handle basic file uploading functionality in a simple web application.