This is an example of an HTML form that allows a user to upload a file:

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

When the user press the submit button, the browser will automatically make a POST request to the /submit-form URL on the same origin of the page, sending the data it contains, not encoded as application/x-www-form-urlencoded as a normal form, but as multipart/form-data.

Server-side, handling multipart data can be tricky and error prone, so we are going to use a utility library called formidable. Here’s the GitHub repo, it has over 4000 stars and well maintained.

You can install it using:

npm install formidable

Then in your Node.js file, include it:

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

Now in the POST endpoint on the /submit-form route, we instantiate a new Formidable form using formidable.IncomingFrom():

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

After doing so, we need to parse the form. We can do so synchronously by providing a callback, which means all files are processed, and once formidable is done, it makes them available:

app.post('/submit-form', (req, res) => {
  new formidable.IncomingFrom().parse(req, (err, fields, files) => {
    if (err) {
      console.error('Error', err)
      throw err
    }
    console.log('Fields', fields)
    console.log('Files', files)
    files.map(file => {
      console.log(file)
    })
  })
})

Or you can use events instead of a callback, to be notified when each file is parsed, and other events, like ending processing, receiving a non-file field, or an error occurred:

app.post('/submit-form', (req, res) => {
  new formidable.IncomingFrom().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()
    })
})

Whatever way you choose, you’ll get one or more Formidable.File objects, which give you information about the file uploaded. These are some of the methods you can call:

  • file.size, the file size in bytes
  • file.path, the path this file is written to
  • file.name, the name of the file
  • file.type, the MIME type of the file

The path defaults to the temporary folder and can be modified if you listen to the fileBegin event:

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