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
- Created a frontend with
npm init vue@latest
- Installed axios with
npm install axios
Backend
Packages used:
- express
- multer
- multer-s3
- aws-sdk
- cors
- Created a backend folder and started with
npm init
- 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:
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.