Editor’s note: This article was last updated by Alexander Godwin on 2 August 2024 to cover handling complex file uploads, such as multiple file uploads and video uploads using Multer, and to answer frequently asked questions about implementing file uploads with Multer, such as how to limit the file size, and how to implement custom filtering.
Multer is a Node.js middleware for handling multipart/form-data
that simplifies the otherwise painstaking process of uploading files in Node.js. In this article, we’ll learn the purpose of Multer in handling files in submitted forms. We’ll also explore Multer by building a mini app with a frontend and backend to test uploading a file.
Let’s get started!
Web applications receive many different types of input from users, including text, graphical controls like checkboxes or radio buttons, and files, like images, videos, and other media.
In forms, each of these inputs is submitted to a server that processes the inputs, uses them in some way, perhaps saving them somewhere else, and then gives the frontend a success
or failed
response.
When submitting forms that contain text inputs, the server (Node.js in our case) has less work to do. Using Express, you can easily grab all the inputs entered in the req.body
object. However, submitting forms with files is a bit more complex because they require more processing, which is where Multer comes in.
All forms include an enctype
attribute, which specifies how data should be encoded by the browser before sending it to the server. The default value is application/x-www-form-urlencoded
, which supports alphanumeric data. The other encoding type is multipart/form-data
, which involves uploading files through forms.
There are two ways to upload forms with multipart/form-data
encoding. The first is by using the enctype
attribute:
<form action='/upload_files' enctype='multipart/form-data'> ... </form>
The code above sends the form data to the /upload_files
path of your application. The second is by using the FormData
API. The FormData
API allows us to build a multipart/form-data
form with key-value pairs that can be sent to the server. Here’s how it’s used:
const form = new FormData() form.append('name', "Dillion") form.append('image', <a file>)
On sending such forms, it becomes the server’s responsibility to correctly parse the form and execute the final operation on the data.
Multer is a middleware designed to handle multipart/form-data
in forms. It is similar to the popular Node.js body-parser, which is built into Express middleware for form submissions. Multer differs in that it supports multipart data, only processing multipart/form-data
forms.
Multer does the work of body-parser by attaching the values of text fields in the req.body
object. It also creates a new object for multiple files, either req.file
or req.files
, which holds information about those files. From the file object, you can pick whatever information is required to post the file to a media management API, like Cloudinary.
Now that we understand the importance of Multer, we’ll build a small sample app to show how a frontend app can send three different files at once in a form, and how Multer can process the files on the backend, making them available for further use.
We’ll start by building the frontend using vanilla HTML, CSS, and JavaScript. Of course, you can easily use any framework to follow along.
First, create a folder called file-upload-example
, with another folder called frontend
inside. In the frontend folder, we’ll have three standard files: index.html
, styles.css
, and script.js
:
<!-- index.html --> <body> <div class="container"> <h1>File Upload</h1> <form id='form'> <div class="input-group"> <label for='name'>Your name</label> <input name='name' id='name' placeholder="Enter your name" /> </div> <div class="input-group"> <label for='files'>Select files</label> <input id='files' type="file" multiple> </div> <button class="submit-btn" type='submit'>Upload</button> </form> </div> <script src='./script.js'></script> </body>
Notice that we’ve created a label and input for Your Name
as well as Select Files
. We also added an Upload
button.
Next, we’ll add the CSS for styling:
/* style.css */ body { background-color: rgb(6, 26, 27); } * { box-sizing: border-box; } .container { max-width: 500px; margin: 60px auto; } .container h1 { text-align: center; color: white; } form { background-color: white; padding: 30px; } form .input-group { margin-bottom: 15px; } form label { display: block; margin-bottom: 10px; } form input { padding: 12px 20px; width: 100%; border: 1px solid #ccc; } .submit-btn { width: 100%; border: none; background: rgb(37, 83, 3); font-size: 18px; color: white; border-radius: 3px; padding: 20px; text-align: center; }
Below is a screenshot of the webpage so far:
As you can see, the form we created takes two inputs: name
and files
. The multiple
attribute specified in the files
input enables us to select multiple files.
Next, we’ll send the form to the server using the code below:
// script.js const form = document.getElementById("form"); form.addEventListener("submit", submitForm); function submitForm(e) { e.preventDefault(); const name = document.getElementById("name"); const files = document.getElementById("files"); const formData = new FormData(); formData.append("name", name.value); for(let i =0; i < files.files.length; i++) { formData.append("files", files.files[i]); } fetch("http://localhost:5000/upload_files", { method: 'POST', body: formData }) .then((res) => console.log(res)) .catch((err) => console.log("Error occured", err)); }
Several important things must happen when we use script.js
. First, we get the form
element from the DOM and add a submit
event to it. Upon submitting, we use preventDefault
to prevent the default action that the browser would take when a form is submitted, which would normally be redirecting to the value of the action
attribute. Next, we get the name
and files
input element from the DOM and create formData.
From here, we’ll append the value of the name input using a key of name
to the formData
. Then, we dynamically add the multiple files we selected to the formData
using a key of files
.
Note: if we’re only concerned with a single file, we can append
files.files[0]
.
Finally, we’ll add a POST
request to http://localhost:5000/upload_files
, which is the API on the backend that we’ll build in the next section.
For our demo, we’ll build our backend using Node.js and Express. We’ll set up a simple API in upload_files
and start our server on localhost:5000
. The API will receive a POST
request that contains the inputs from the submitted form.
To use Node.js for our server, we’ll need to set up a basic Node.js project. In the root directory of the project in the terminal at file-upload-example
, run the following code:
npm init -y
The command above creates a basic package.json
with some information about your app. Next, we’ll install the required dependency, which, for our purposes, is Express:
npm i express
Next, create a server.js
file and add the following code:
// server.js const express = require("express"); const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.post("/upload_files", uploadFiles); function uploadFiles(req, res) { console.log(req.body); } app.listen(5000, () => { console.log(`Server started...`); });
Express contains the bodyParser
object, which is a middleware for populating req.body
with the submitted inputs on a form. Calling app.use(express.json())
executes the middleware on every request made to our server.
The API is set up with app.post('/upload_files', uploadFiles)
. uploadFiles
is the API controller. As seen above, we are only logging out req.body
, which should be populated by epxress.json()
. We’ll test this out in the example below.
body-parser
in ExpressIn your terminal, run node server
to start the server. If done correctly, you’ll see the following in your terminal:
You can now open your frontend app in your browser. Fill in both inputs in the frontend, the name and files, then click submit. On your backend, you should see the following:
The code in the image above means that the req.body
object is empty, which is to be expected. If you’ll recall, body-parser
doesn’t support multipart
data. Instead, we’ll use Multer to parse the form.
Install Multer by running the following command in your terminal:
npm i multer
To configure Multer, add the following to the top of server.js
:
const multer = require("multer"); const upload = multer({ dest: "uploads/" }); ...
Although Multer has many other configuration options, we’re only interested in the dest
property for our project, which specifies the directory where Multer will save the encoded files.
Next, we’ll use Multer to intercept incoming requests on our API and parse the inputs to make them available on the req
object:
app.post("/upload_files", upload.array("files"), uploadFiles); function uploadFiles(req, res) { console.log(req.body); console.log(req.files); res.json({ message: "Successfully uploaded files" }); }
To handle multiple files, use upload.array
. For a single file, use upload.single
. Note that the files
argument depends on the name of the input specified in formData
.
Multer will add the text inputs to req.body
and add the files sent to the req.files
array. To see this at work in the terminal, enter text and select multiple images on the frontend, then click submit and check the logged results in your terminal.
As you can see in the example below, I entered Images
in the text
input and selected a PDF, an SVG, and a JPEG file. Below is a screenshot of the logged result:
For reference, if you want to upload to a storage service like Cloudinary, you will have to send the file directly from the uploads folder. The path
property shows the path to the file.
As we mentioned above, to upload a single file to your Node.js server, you’d use multer.single
as opposed to multer.array
.
In the following code block, let’s update our frontend code to contain a field that will accept just one file instead of multiple:
//index.html <div class="input-group"> <label for='single-file'>Select Single file</label> <input id='single-file' type="file"> </div>
Update the script.js
file as follows:
const singleFile = document.getElementById("single-file") formData.append("singleFile", singleFile.files[0]) //update the url where we'll send the request to fetch("http://localhost:8000/upload_single", { method: "POST", body: formData, })
On the Express server, update app.js
to handle single file uploads to the upload_single
route as follows:
app.post("/upload_single", upload.single("singleFile"), uploadSingle) function uploadSingle(req, res) { console.log("req file", req.file) res.json({ message: "Successfully uploaded single file" }) }
Notice that we are accessing the files with req.file
rather than req.files
as shown previously, which is because we’re using Multer’s upload.single
function to accept a single file.
We’ve seen Multer’s feature for single file uploads and uploading an array of files on a single field, but what if our API endpoint requires multiple fields to have file uploads, whether single or array files? Multer provides an upload.fields()
method to allow us to handle these scenarios.
We’ll update our app.js
file as follows:
app.post( "/upload_multiple", upload.fields([ { name: "singleFile", maxCount: 1 }, { name: "files", maxCount: 5 }, ]), multipleUploads ) function multipleUploads(req, res) { console.log("singleFile", req.files.singleFile) console.log("files", req.files.files) res.json({ message: "Successfully uploaded Multiple files" }) }
Update script.js
on the frontend to make a request to the /upload_multiple
endpoint:
fetch("http://localhost:8000/upload_multiple", { method: "POST", body: formData, })
req.files.singleFile
contains the file for the singleFile
field, while req.files.files
has the value of the uploads for the files
field:
Our API can already accept video files but we can add some filters to make sure that we receive video files that meet our requirements:
const path = require('path'); //... const fileFilter = (req, file, cb) => { const filetypes = /.mp4|.avi|.mkv/ const extname = filetypes.test(path.extname(file.originalname).toLowerCase()) if (extname) { return cb(null, true); } else { cb('Error: Videos Only!'); } }; const upload = multer({ dest: "uploads/", fileFilter, limits: { fileSize: 100000000 }, }) app.post("/upload_video", upload.single("video"), (req, res) => { if (req.file) { console.log("video", video) res.json({ message: "Video uploaded successfully!", }) } else { res.status(400).json({ message: "Failed to upload video" }) } })
Then, update the frontend as follows:
//index.html <div class="input-group"> <label for='video'>Select video</label> <input id='video' type="file"> </div> //script.js const video = document.getElementById("video") formData.append("video", video.files[0]) //update the url as follows fetch("http://localhost:8000/upload_video", { method: "POST", body: formData, })
When allowing users to upload files to our servers, we often have to limit the size of those uploads so that they don’t take up too much storage space or slow down the network. To limit file size, run the following code:
const upload = multer({ dest: "uploads/", limits: { fileSize: 100000000 }, //limits file to 100mb })
filefilter
?Implementing a file filter is important to control the type of files that are uploaded to our servers. Multer’s fileFilter
can be used as follows:
const fileFilter = (req, file, cb) => { const filetypes = /.mp4|.avi|.mkv/ const extname = filetypes.test(path.extname(file.originalname).toLowerCase()) if (extname) { return cb(null, true); } else { cb('Error: Videos Only!'); } }; const upload = multer({ dest: "uploads/", fileFilter, })
req.file
is undefined?Here are a couple of things that can be done when req.file
is undefined:
upload.single
rather than upload.array
which would have to be accessed using req.files
multer
and correctly added it to the relevant routesFor text inputs alone, the bodyParser
object used inside of Express is enough to parse those inputs. They make the inputs available as a key-value pair in the req.body
object. Multer comes in handy when forms contain multipart
data that includes text inputs and files, which the body-parser
library cannot handle.
With Multer, you can handle single or multiple files in addition to text inputs sent through a form. Remember that you should only use Multer when you’re sending files through forms because Multer cannot handle any form that isn’t multipart.
In this article, we’ve seen a brief of form submissions, the benefits of body parsers on the server, and the role that Multer plays in handling form inputs. We also built a small application using Node.js and Multer to demonstrate the file upload process.
For the next steps, you can look at uploading to Cloudinary from your server using the Upload API reference. I hope you enjoyed this article! Happy coding!
Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third-party services are successful, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.
LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowHandle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
Design React Native UIs that look great on any device by using adaptive layouts, responsive scaling, and platform-specific tools.
Angular’s two-way data binding has evolved with signals, offering improved performance, simpler syntax, and better type inference.
6 Replies to "Multer: Easily upload files with Node.js and Express"
Using this tutorial, I am seeing that my images files are correctly coming through in the request body within an array “images” (created on front end with each file from the file input’s filelist). However, Multer is returning an empty array for within req.files
You must remove the “Content-Type” header or else the request fails with a 500. And you have to have the response body without a comma at the end:
{
method: ‘POST’,
body: formData
}
Hello I’m getting an error at the server saying ‘unexpected end of form” please help
When I open the frontend app in browser using URL=http://localhost:5000/upload_files, there is error Cannot GET /upload_files. Can you pls help?
i don’t know why suddenly my multer stops working, i use single upload in server and form data from front end, any suggestion ?
thank you for this useful tutorial, it really helps me upload picture to server, hope to find more interesting thing here