Handling file uploads in forms using Express
How to manage storing and handling files uploaded via forms, in Express
This is an example of an HTML form that allows a user to upload a file:
<form method="POST" action="/submit-form" enctype="multipart/form-data">
<input type="file" name="document" />
<input type="submit" />
</form>
Don’t forget to add
enctype="multipart/form-data"
to the form, or files won’t be uploaded
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. The browser sends the data contained, not encoded as as a normal form application/x-www-form-urlencoded
, 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 is well-maintained.
You can install it using:
npm install formidable
Then include it in your Node.js file:
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.IncomingForm()
:
app.post('/submit-form', (req, res) => {
new formidable.IncomingForm()
})
After doing so, we need to be able 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.IncomingForm().parse(req, (err, fields, files) => {
if (err) {
console.error('Error', err)
throw err
}
console.log('Fields', fields)
console.log('Files', files)
for (const file of Object.entries(files)) {
console.log(file)
}
})
})
Or, you can use events instead of a callback. For example, to be notified when each file is parsed, or other events such as completion of file processing, receiving a non-file field, or if an error occurred:
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()
})
})
Whichever 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 bytesfile.path
, the path the file is written tofile.name
, the name of the filefile.type
, the MIME type of the file
The path defaults to the temporary folder and can be modified if you listen 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)
})
//...
})
→ I wrote 17 books to help you become a better developer, download them all at $0 cost by joining my newsletter
→ JOIN MY CODING BOOTCAMP, an amazing cohort course that will be a huge step up in your coding career - covering React, Next.js - next edition February 2025