Uploading multiple files to S3 in Vue and Node

August 27, 2022

A question on Stack Overflow got me interested in solving a problem with uploading several pictures in Vue, so I decided to spin up a small project handling this, with Vue 3 and the composition API, and a quick Node backend using Express.

Prequisities

AWS

  • IAM user with Admin access, access key and secret key
  • S3 Bucket created

Frontend

Packages used:

  • vue@latest
  • axios
  1. Created a frontend with npm init vue@latest
  2. Installed axios with npm install axios

Backend

Packages used:

  • express
  • multer
  • multer-s3
  • aws-sdk
  • cors
  1. Created a backend folder and started with npm init
  2. Installed required packages npm install multer multer-s3 aws-sdk express cors

Simple frontend file input

Let’s create a small frontend that can handle multiple files, showing us which files have been added and a simple button to upload. It will simply look like this:

Frontend multi-upload

The code for this is rather simple, and in the newly initialized App.vue

<template>
  <main>
    <div style="display: flex; justify-content: center; align-items: center; flex-direction: column; ">
      <div style="margin-bottom: 4rem;">
        <input multiple type="file" @change="addFiles"/>

      </div>
      <div>
        <h2>Added files:</h2>
        <div v-for="file in state.files">
          {{ file.name }}
        </div>
      </div>
      <div>
        <button @click="uploadFiles">Upload files</button>
      </div>
    </div>
  </main>
</template>

The most important thing to remember in the frontend is the <input multiple type="file" @change="addFiles" />. The input must be able to handle several inputs.

There’s only two functions in this component. The addFiles function fires when the input changes, and since file inputs cannot use v-model we must use the @change instead.

First we need to initialize a new variable that handles the files:

<script setup>
import {reactive} from "vue";

let files = []
const state = reactive({files})
</script>

The reason that we also at the same time initialize the reactive({files}) is because we want to be able to show what has been added in the frontend, and the original let files = [] is not reactive. When a new file is added to the input, we also want to add it to our files variable, so we simply push it to the reactive state on the change:

<script setup>
import {reactive} from "vue";

let files = []
const state = reactive({files})

const addFiles = (event) => {
  state.files.push(event.target.files[0])
}
</script>

This will update both files, and the state.

Upload from frontend to backend

Last but not least, we need to add the uploadFiles function that is fired when we click the Upload files button.

<script setup>
import {reactive} from "vue";
import axios from 'axios'

let files = []
const state = reactive({files})

const addFiles = (event) => {
  state.files.push(event.target.files[0])
}

const uploadFiles = async () => {
  const formData = new FormData()
  for (const i in files) {
    formData.append('file', files[i])
  }
  await axios.post('YOUR_BACKEND_URL/api/upload', formData, {
        headers: {
          'Content-Type': 'multipart/form-data',
        },
      },
  )
}
</script>

Here we simply append the files to our form-data. This is one way to do it in axios, but they have also introduced axios.postForm, however, I’m more comfortable using form-data.

Remember to change YOUR_BACKEND_URL.

Backend upload routes to S3

Now to be able to add actually upload from the backend to S3, we need to introduce an Express route for it. I modified index.js to add a new route.

const http = require('http');
const express = require("express");
const multer = require("multer");
const multerS3 = require("multer-s3")
const AWS = require('aws-sdk')
const cors = require('cors')

const app = express();
app.use(cors())
app.use(express.json());

const s3 = new AWS.S3({
    accessKeyId: 'YOUR_AWS_ACCESS_KEY',
    secretAccessKey: 'YOUR_SECRET_ACCESS_KEY'
});

const uploadS3 = multer({
    storage: multerS3({
        s3: s3,
        bucket: 'YOUR_BUCKET_NAME',
        metadata: (req, file, callBack) => {
            callBack(null, {fieldName: file.fieldname})
        },
        key: (req, file, callBack) => {
            callBack(null, file.originalname)
        }
    })
});

app.post("/api/upload",  uploadS3.array('file', 10), async (req, res) => {
    return res.status(200).send('OK')
})

const server = http.createServer(app);

server.listen(process.env.PORT || 4000, () => {
  console.log(`Server running on port ${4000}`);
});

I’ll shortly explain this:

First we initialize the aws-sdk with our access key and secret. After we’ve done that, we need set up multer, simply a middleware for handling multipart/form-data, which we do together with multer-s3.

app.post("/api/upload") then gives us the route. Here it is important the name inside array in the upload function is the same as the form-data name that upload in the frontend, that is file in this example. Otherwise we won’t be able to find the files uploaded.

Wrap-up

Here I have introduced a way to upload multiple files from a Vue frontend to a node backend.


Profile picture

Written by An anonymous coder who lives and works building useful things. Mostly focusing on Django, Vue and overall Python examples.