File Uploads with Express

In order to follow along with this activity, I assume you've already completed the following steps to set up the project:

  1. Setting up the project
  2. File IO in NodeJS
  3. A very simple web server
  4. NPM
  5. Creating custom modules
  6. Getting started with Express

In order to handle file uploads in Express we'll have to install the express-fileupload package by running this command:

npm install express-fileupload

Create a file named file-upload-sample.js in the samples folder, and put this code into it:

const path = require("path");
const express = require('express');
const app = express();

// set the 'public' folder as the location for our static files: 
app.use(express.static('public'));

// set up for file uploads
const fileUpload = require('express-fileupload');
app.use(fileUpload());

app.post('/upload', (req, res) => {

	if (!req.files) {
    return res.status(400).send("No files were uploaded.");
  }

  res.send(JSON.stringify(req.files));
});

// START THE SERVER
const port = 8080; // We'll run the server on port 8080
const server = app.listen(port, () => {
   console.log("Waiting for requests on port %s", port);
});

This Express web application does the following:

  1. Sets the 'public' folder for static files
  2. Imports the express-fileupload package and configures the app to 'use' it.
  3. Defines a route (/upload) that handles POST requests. Currently the code in the callback for the route simply checks the request object's files property, which will be an object that contains information about each file that is being uploaded to the server. The files property is stringified and sent in the response so that we can analyze it.

But before can run the code, we need to create a form that allows users to upload files.

Add a file named file-upload.html to the public folder, and put this code into it:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>File Upload Example</title>
  <style>
    label{
        display:block; 
        font-weight: bold;
    }

    input[type=checkbox] + label, input[type=radio] + label{
        display: inline;
        font-weight: normal;
    }
  </style>
</head>
<body>
<h3>File Upload</h3>
<form method="POST" action="/upload" enctype="multipart/form-data">
    <label>Upload a file</label>
    <input type="file" name="someFile">
    <br>
    <input type="submit">
</form>
</body>
</html>

The form has only one input (other than the submit button), which has it's type attribute set to file and it's name attribute set to somefile. If you want, you could add other inputs to the form and handle them just as we did in the previous activity.

Note that form element's action attribute is set to /upload, which is the route we defined in our sample Express application.

Also note that the form has an enctype attribute that is set to multipart/form-data. If you don't include this attribute, then your file(s) will not get sent when you submit the form.

Now we're ready to run the application and try uploading a file.

Start the sample app by running this command: node samples/file-upload-sample-app.js.

Then navigate to the form in your browser (localhost:8080/file-upload.html)

Click on the 'Choose File' button, which is the file input element in the form, and then select a file from your computer by using the explorer window that appears.

Then submit the form.

You should end up at the /upload route, which will display a stringified object that has a property named someFile (this is the name attribute of the file input element). This property is a 'file' object that contains some information about the file you just sent to the server, as well as the data that makes up the file (this is the 'buffer' data, which is the binary content of the file - for more information check out this link on buffers in NodeJS).

The 'file' object also has a mv() method which you don't see when it's stringified. But we'll be using the method shortly.

So now that we are sending files to our Express application, let's update it so that it saves the file on the server.

We need to create a folder where the uploaded file will end up, so create a folder named uploaded-files inside the project folder (node-sample-project).

Now update the route in file-upload-sample-app.js to look like this:

app.post('/upload', (req, res) => {

	if (!req.files) {
    return res.status(400).send("No files were uploaded.");
  }

  //res.send(JSON.stringify(req.files));

  const file = req.files.someFile;
  
  // TODO: validate the file type 

  // save the file
  const filePath = __dirname + "/../uploaded-files/" + file.name; // dirname will not end with a / (so you need to add one)
  file.mv(filePath, (err) => {
    if (err) {
      return res.status(500).send(err);
    }
    return res.send({ status: "success", path: filePath });
  });
});

Focus on the code under the comment // save the file. This code defines filePath constant, which includes a path to the uploaded-files folder and appends to it the name of the file that is being uploaded. This is where the uploaded file will end up.

Then we call the mv() method (which is short for 'move'), which moves the file to path specified by the filePath variable. This is an asynchronous function that takes two params. The first is the path to move the file to, and the second is the callback that will be triggered once the file IO operation completes.

Restart the server (node samples/file-upload-sample-app.js), then navigate to localhost:8080/file-upload.html in your browser and upload a file again. Hopefully you'll see a message saying that it was a success.

Now look inside the uploaded-files folder, and you should see the file that you just uploaded.

You generally don't want to include uploaded files in your git repository, so you should add this to your .gitignore file:

uploaded-files/

Allowing files to be uploaded to your server can be very dangerous so you should be careful to only allow specific types of files to be uploaded.

Replace the comment //TODO: comment in file-upload-sample-app.js with this code:

// validate the file type 
const allowedExtension = ['.png','.jpg','.jpeg']; // these files extensions are allowed to be uploaded

const extensionName = path.extname(file.name); // get the file extension of file being uploaded

if(!allowedExtension.includes(extensionName)){
    return res.status(422).send("Invalid Image");
}

This code defines an array of allowed file extensions. Then it extracts the extension of the file being uploaded and checks to see if it's included in the array. If not, it sends a status code of 422 back to the browser.

Now if you restart the server, you should only be allowed to upload .png, .jpg, and .jpeg files (basic image files).

There are other things you can do to protect your server from problematic file uploads. If you are interested, here's more info on Express file uploads.