Learn how to handle and store files uploaded via forms in Express.

This is an example of an HTML form that allows users to upload files:

<form method="POST" action="/submit-form" enctype="multipart/form-data">
  <input type="file" name="document" />
  <input type="submit" />
</form>

Don’t forget to include enctype="multipart/form-data" in the form, otherwise files won’t be uploaded.

When the user presses the submit button, the browser will send a POST request to the /submit-form URL on the same origin of the page. The data is sent as multipart/form-data instead of the normal form application/x-www-form-urlencoded encoding.

Handling multipart data on the server-side can be challenging and error-prone. To simplify this process, we will use a utility library called formidable. You can find the GitHub repository here, which has over 4000 stars and is well-maintained.

To install formidable, run the following command:

npm install formidable

Next, include the formidable module in your Node.js file:

const express = require('express');
const app = express();
const formidable = require('formidable');

Now, in the POST endpoint for the /submit-form route, create a new instance of Formidable using formidable.IncomingForm():

app.post('/submit-form', (req, res) => {
  new formidable.IncomingForm();
});

After instantiating the Formidable form, we need to be able to parse the form data. We can do this synchronously by providing a callback function. This means that all files are processed, and once formidable has finished, they become available:

app.post('/submit-form', (req, res) => {
  new formidable.IncomingForm().parse(req, (err, fields, files) => {
    if (err) {
      console.error('Error', err);
      throw err;
    }
    console.log('Fields', fields);
    console.log('Files', files);
    for (const [name, file] of Object.entries(files)) {
      console.log(name, file);
    }
  });
});

Alternatively, you can use events instead of a callback. For example, to be notified when each file is parsed or for other events such as file processing completion, receiving a non-file field, or encountering an error:

app.post('/submit-form', (req, res) => {
  new formidable.IncomingForm().parse(req)
    .on('field', (name, field) => {
      console.log('Field', name, field);
    })
    .on('file', (name, file) => {
      console.log('Uploaded file', name, file);
    })
    .on('aborted', () => {
      console.error('Request aborted by the user');
    })
    .on('error', (err) => {
      console.error('Error', err);
      throw err;
    })
    .on('end', () => {
      res.end();
    });
});

Regardless of which approach you choose, you’ll receive one or more Formidable.File objects, which provide information about the uploaded file. Here are some of the methods you can use:

  • file.size: the file size in bytes
  • file.path: the path where the file is written to
  • file.name: the name of the file
  • file.type: the MIME type of the file

By default, the file is stored in the temporary folder. You can modify the file path by listening for the fileBegin event:

app.post('/submit-form', (req, res) => {
  new formidable.IncomingForm().parse(req)
    .on('fileBegin', (name, file) => {
      file.path = __dirname + '/uploads/' + file.name;
    })
    .on('file', (name, file) => {
      console.log('Uploaded file', name, file);
    })
    // ...
});