How to Upload Files Using JavaScript

In this lesson, we are going to cover how to upload files to the server using the multipart/form-data encoding, which we discussed in the previous lesson.

Updated database and model

First, you need a new structure for our users table. Since we haven't discussed how to run database migrations yet, let's delete our old database.sqlite and create a new one.

Next, add a new picture column in our users table.

models/user.js

javascript
1db.serialize(() => {
2  db.run(
3    `CREATE TABLE IF NOT EXISTS users (
4      id INTEGER PRIMARY KEY AUTOINCREMENT,
5      username TEXT,
6      email TEXT,
7      picture TEXT
8    )`
9  );
10});

You should know that the database cannot store the actual image file. The files should be saved inside the server's file system, and this picture column will simply store the path to the image file.

The corresponding create() method will become:

javascript
1static create(username, email, filePath, callback) {
2  const sql = "INSERT INTO users (username, email, picture) VALUES (?, ?, ?)";
3  db.run(sql, [username, email, filePath], function (err) {
4    callback(null, this.lastID);
5  });
6}

New form

Next, our user creation form also requires some modification.

views/index.pug

pug
1extends layout.pug
2
3block meta
4  title Home
5
6block content
7
8  form(id="myForm")
9    label(for="username") Username:
10    input(type="text", name="username", id="username", value="")
11    label(for="email") Email:
12    input(type="email", name="email", id="email", value="")
13    label(for="picture") Picture:
14    input(type="file", name="picture", id="picture", value="")
15    input(type="submit", value="Submit")
16
17  script.
18    . . .

This template will be rendered into:

html
1<form id="myForm">
2  <label for="username">Username:</label>
3  <input type="text" name="username" id="username" value="" />
4
5  <label for="email">Email:</label>
6  <input type="email" name="email" id="email" value="" />
7
8  <label for="picture">Picture: </label>
9  <input type="file" name="picture" id="picture" value="" />
10
11  <input type="submit" value="Submit" />
12</form>

In this case, we are going to use JavaScript and the fetch() method to submit this form.

javascript
1document.addEventListener("DOMContentLoaded", function () {
2  const form = document.getElementById("myForm");
3  const usernameInput = document.getElementById("username");
4  const emailInput = document.getElementById("email");
5  const pictureInput = document.getElementById("picture");
6
7  const formData = new FormData();
8
9  usernameInput.addEventListener("input", function () {
10    formData.set("username", usernameInput.value);
11  });
12
13  emailInput.addEventListener("input", function () {
14    formData.set("email", emailInput.value);
15  });
16
17  pictureInput.addEventListener("change", function () {
18    formData.set("picture", pictureInput.files[0]);
19  });
20
21  form.addEventListener("submit", async function (event) {
22    event.preventDefault(); // Prevent the default form submission
23
24    await fetch("/users", {
25      method: "POST",
26      body: formData,
27    });
28  });
29});

Line 7, since we are submitting the form in multipart/form-data encoding, you must use FormData() instead of URLSearchParams().

Line 18, pictureInput.files[0] accesses the first file from an array of files. The reason that this is necessary is because the same <input> element can be used to upload multiple files by adding a multiple attribute.

Also, notice that we did not include a custom header in the fetch() method. That is because when using FormData(), fetch() can automatically handle the Content-Type header. Manually setting it could lead to potential errors in this case.

Updated controller

Lastly, we also need to consider how can Express process the submitted data in the backend. Unlike the application/x-www-form-urlencoded encoding, Express does not support multipart/form-data natively. Instead, you'll need to rely on an external package called multer.

Install the package with the following command:

bash
1npm install multer

And then, go to index.js. This is where you need to configure multer and add it as a middleware.

javascript
1import multer from "multer";
2
3const storage = multer.diskStorage({
4  destination: function (req, file, callback) {
5    callback(null, "uploads/"); // Configures the upload path
6  },
7  filename: function (req, file, callback) {
8    callback(null, Date.now() + "-" + file.originalname); //Configures the custom file name
9  },
10});
11
12const upload = multer({ storage: storage });

Also remember to create the corresponding uploads directory.

bash
1mkdir uploads

Next, add upload as a middleware.

javascript
1app
2  .route("/users")
3  .get(userController.getAllUsers)
4  .post(upload.single("picture"), userController.createNewUser); // <==

The single() method means that we are only uploading one file.

And finally, update the createNewUser() controller method.

controllers/userController.js

javascript
1createNewUser: async function (req, res) {
2  const { username, email } = req.body;
3  const file = req.file;
4
5  User.create(username, email, file.path, (err, userID) => {
6    res.redirect(303, `/users/${userID}`);
7  });
8},

Start the dev server, and go to http://localhost:3001.

image upload form

Submit the form, and a new file should appear under the uploads directory.

text
1.
2├── controllers
3│   └── userController.js
4├── database.sqlite
5├── index.js
6├── models
7│   └── user.js
8├── package.json
9├── package-lock.json
10├── uploads
11│   └── xxxx-xxxx.jpg
12└── views
13    ├── index.pug
14    ├── layout.pug
15    └── user.pug

And inside the users table, a new user record should be created. Its picture key should point to the uploaded file.

The picture key