web #node.js #javascript #html5

Multiple File Upload to Node.js Using a Single INPUT HTML Element

Jan 5 '19 · 10 min read · 1296

Forget Multer, use the express-fileupload module and a single INPUT HTML element to make multiple file uploads as easy as they should be!

Intro

S

ooner or later, you will want to allow your users to upload multiple files at once. I mean, can you imagine uploading a file at a time to Dropbox? 🤢😡

Multiple file upload has a massive UX gain, yet it is still something that developers can still struggle with, without an out of the box solution. Since the arrival of HTML5, the biggest browsers have implemented a new method for the INPUT element to allow multiple files to be uploaded using a single INPUT element.

HTML5 Multiple File Input

<!-- IMPORTANT:  FORM's enctype must be "multipart/form-data" -->
<form method="post" action="/upload" enctype="multipart/form-data">
  <input name="someFiles" type="file" multiple="multiple"/>
</form>

So this is a tick in the box for ease of use, but is just half the story; what about accepting and handling uploads on the server?

Handling Multiple File Uploads with Node.js

There are a lot of tutorials that make use of the 'multer' module for handling multiple incoming files, but I have recently found a lesser known module that I feel is easier to use: 'express-fileupload'.

Promisifying the 'express-fileupload' module - 'fileupload-promisify.js'

This module is not async/await ready and so I create a 'fileupload-promisify.js' utility script that contains the express-fileupload module and two asynchronous methods: saveFile and saveFiles. Below is code used for this blog when I am creating and editing posts as an admin user.

const fileUpload = require('express-fileupload');

const saveFile = async (file, path) => {
  file.mv(`${path}/${file.name}`, (err) => {
    if(err){
      console.log(err);
      throw new Error(err);
    }
  });
};

const saveFiles = async (filesArray, path) => {
  for (const file of filesArray) {
    await saveFile(file, path);
  }
};

module.exports = {
  fileUpload,
  saveFile,
  saveFiles
}

Using the fileupload middleware

The middleware creates a req.files object containing a property - with the same name as the INPUT's name attribute - for each of the different form INPUT elements. If the INPUT is marked as a multiple file INPUT element, it's files are always represented by an array in the req.files object.

const {fileUpload, saveFile, saveFiles} = require('./utils/fileupload-promisify');

app.use(fileUpload());

app.post('/upload', async (req, res) => {
  const {someFiles} = req.files;
  try {
    if (Object.keys(req.files).length === 0)
      return res.status(400).send('No files were uploaded.');

    // Save uploaded files
    if (Array.isArray(someFiles))
      await saveFiles(someFiles, `${publicPath}/posts/${id}`);
    else
      await saveFile(someFiles, `${publicPath}/posts/${id}`);

    res.redirect('/');
  } catch (e) {
    res.status(400).send();
  }
});

Here we first check that a file was actually uploaded. Then, we check whether we need to use the saveFile or saveFiles async method depending on whether more than one file is being uploaded.