File Upload and Download in Node.js using Multer

Shubham Sharma
Geek Culture
Published in
6 min readMar 28, 2021

Uploading or downloading files in your application is not something which you would use very rarely. Thus, it is important to know how to upload files at the client-side and how to handle and store files at the server-side. Also, while fetching the file stored at the server, one has to be aware of the different possibilities like should the downloaded file open in one of the browser tabs or it should be downloaded as an attachment and opened separately.

File upload at the client side:

For explaining the file upload at the client side, code snippet of Vue.js has been used as an example below. Inside the form element, enctype="multipart/form-data" signifies that what is submitted through the form is not just text but also files. If one wants to submit only text in the form, one should use enctype="x-www-urlencoded" as that would be efficient.

Vue.js snippet for uploading a file

The input element has a type of "file" which represents that this input element does not take text input but instead a file explorer opens up on clicking the input box corresponding to the input element. When you select a file from the explorer and click Open, then @change hook gets triggered and it calls the attached functions and statements. Here, the attached function filesChange takes two arguments, name of element and list of files. Name of input element for the case of above input element would be "photo", as that has been passed as name="photo" in the input element. List of files would represent an array which contains all the files which one had selected in the file explorer.

Now coming to the method filesChange, we create a JS object called FormData. FormData is an iterable object which replicates forms. It can be seen as an array of arrays. Each one array would represent the elements which one wants to send to the server. Each one array has a name and a value. One optional param is fileName. These are added by the .append(name, value, ?fileName) method to formData. Name represents the name of the input element and value either represents a string or a BLOB (Binary Large Object) and fileName is the name of the file with which it would be received on the server.

Send the selected files to the backend server in a post request

Now, the next task is to post the uploaded file to the server where it would be handled appropriately. We use axios which is an http client and can be used to send http requests. Here, we use axios.post(url, formData) method since we are adding something thus HTTP verb POST. We need a backend server running so that the file which we are sending from the client side is handled properly at the server.

File handling at the backend side:
We have used Express server at the server side. At the backend stage, one has to map the routes with appropriate controller functions. The route in our case is ‘/upload’. We will touch upon a node.js library called multer. We use multer to extract incoming files. Multer is a middle-ware and extracts the uploaded file from HTTP request

One can install multer by

npm install multer — save

Now we can import multer in our routes file as

const multer = require(‘multer’)
app.use(multer().single(‘photos’))

Here, app is an instance of express object. .single(name) specifies that we want only one file to be uploaded for a particular input element name which is ‘photos’ for our case. Now once the multer middleware is applied, we can access the file using req.file. req.file contains

fieldname: input element name against which the file was uploaded
originalname: original name of the file. The name which server uses to store the file can be different from the original name
encoding: number of bits used for encoding
mimetype: which type of file was this
buffer: file was sent to the server as a stream and buffer contains that stream.

The buffer stream can be converted into binary data if we use dest param in multer

app.use(multer({dest: ‘destinationFolder’}).single(‘photos’))

The destinationFolder inside the root of our backend project will be the place where the file sent as buffer stream would be stored as a binary object. In that case, we would not see the buffer key in the req.file.

To get more options around how the incoming file would be stored, we can use .diskStorage method of multer

destination is a function which takes http req variable, file and a cb (callback) as param. The callback in turn takes error (if any, null if no error) as the first param and destination folder as the place where one wants to store the files. Similarly, the filename function also takes http req variable, file and a cb (callback) as params. This callback again takes error (if any, null if no error) as the first param and the name by which we have to store the file as the second param. Here, we have appended timestamp to the file’s original name to prevent any clash arising from repeated uploads of the same file.
In the same fashion we can add a file filter which would disallow upload of file which do not match a particular mimetype. In this example, we allow only images of png, jpg and jpeg type.

Now since we have stored the file in the server file system, the next thing would be to download the file from front-end through another http request

To download the file, we need to use an inbuilt library of JS called ‘fs’ or file system. It allows us to file system operations. To download the file at a particular path, we just need to put the below code in the right controller.

const fs = require('fs')

We set ‘Content-Disposition’ header. ‘attachment’ means that the file will not be opened in the browser but it will be downloaded first before opening.
The variable filename contains the name of the file by which the new file would be downloaded. This would work.

But here we are reading whole file in one go through readFile. For bigger files, it will take long for the file to get downloaded. Worse than that, sometimes the memory on the server might overflow for many incoming requests as it has to read all the data in memory.
Thus, to improve it, we must use file stream

In the above piece of code, we first create a file read stream as we want to read data from a specific path. The param path contains the relative address of the file to be downloaded in the form of string. Again, we set the ‘Content-Disposition’ header to ‘attachment’ so that the file gets downloaded as an attachment with the name as the variable filename holds. pipe method takes a read stream and puts its content into a write stream. Here, http res is a write stream so we write the entire data of readable file stream to http response res. Now try running the code, a file explorer would open and you can specify the folder where you want to store the file. After doing that, the file would be dowloaded and would appear in the bottom of your browser as this.

Summary

That is it for the blog. We learnt about uploading file from the front-end. At the backend level, we handled file upload through a nice middleware called multer which automatically extracts file and stores them as specified by us. It can filter certain file types for us. We also learnt about how to download files by creating read file streams and using pipe to write that stream to HTTP response. For reference, on can refer to my code in the following git repository

--

--