Back to Course |
Vue.js 3 + Laravel 11 + Vite: SPA CRUD

File Upload Example

In this lesson, let's see how we can upload files using Vue.js. It's a pretty complex topic because it isn't just adding a new field. Let's see how we can do that.

vue file upload


Vue: File Upload Input

First, let's add a new file upload field and call it thumbnail.

resources/js/components/Posts/Create.vue:

<template>
<form @submit.prevent="storePost(post)">
// ...
 
<!-- Thumbnail -->
<div class="mt-4">
<label for="thumbnail" class="block font-medium text-sm text-gray-700">
Thumbnail
</label>
<input @change="post.thumbnail = $event.target.files[0]" type="file" id="thumbnail" />
<div class="text-red-600 mt-1">
<div v-for="message in validationErrors?.thumbnail">
{{ message }}
</div>
</div>
</div>
 
<!-- Buttons -->
<div class="mt-4">
<button :disabled="isLoading" class="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm uppercase text-white disabled:opacity-75 disabled:cursor-not-allowed">
<span v-show="isLoading" class="inline-block animate-spin w-4 h-4 mr-2 border-t-2 border-t-white border-r-2 border-r-white border-b-2 border-b-white border-l-2 border-l-blue-600 rounded-full"></span>
<span v-if="isLoading">Processing...</span>
<span v-else>Save</span>
</button>
</div>
</form>
</template>
 
<script setup>
// ...
const post = reactive({
title: '',
content: '',
category_id: '',
thumbnail: ''
})
// ...
</script>

The problem with the file input is that we cannot use v-model="thumbnail". As you can see instead of v-model we used @change="post.thumbnail = $event.target.files[0]". So what does it mean?

We listen for a change on the input file and then manually assign it to post.thumbnail. We get two things with this change event:

  • the target which is this input
  • and select the first file from an array: there could be multiple files if it's allowed by the input, so we need to specify which file exactly.

Laravel: Simple File Upload

Now, let's take a look at the back-end. In this lesson, we will not implement the full file upload system to the server because this course is not about file uploading.

You can use a package like spatie/media-library or something else. I will just show you how to catch the file and we'll log the filename.

In the PostController, we just need to check if the request has a file, and if it does then perform the upload.

app/Http/Controllers/Api/PostController.php:

class PostController extends Controller
{
// ...
public function store(StorePostRequest $request)
{
if ($request->hasFile('thumbnail')) {
$filename = $request->file('thumbnail')->getClientOriginalName();
info($filename);
}
$post = Post::create($request->validated());
 
return new PostResource($post);
}
}

Vue: Composable Method

Now, in the posts composable we need to post that file a bit differently. For that, we need to make a FormData Object and pass that to an HTTP request instead of passing the post.

resources/js/composables/posts.js:

import { ref } from 'vue'
import { useRouter } from 'vue-router'
 
export default function usePosts() {
// ...
const storePost = async (post) => {
if (isLoading.value) return;
 
isLoading.value = true
validationErrors.value = {}
 
let serializedPost = new FormData()
for (let item in post) {
if (post.hasOwnProperty(item)) {
serializedPost.append(item, post[item])
}
}
 
axios.post('/api/posts', post)
axios.post('/api/posts', serializedPost)
.then(response => {
router.push({ name: 'posts.index' })
})
.catch(error => {
if (error.response?.data) {
validationErrors.value = error.response.data.errors
isLoading.value = false
}
})
}
 
return { posts, getPosts, storePost, validationErrors, isLoading }
}

Here, we first create a new FormData Object. Then, we have a for loop for a post object and check if the post has an item, then we append it to serializedPost. And in the Axios POST request, instead of the post, we pass the serializedPost.

Now, if you upload a file and create a post, you should see the uploaded file name in the Laravel log file.

[2023-04-29 13:47:15] local.INFO: Screenshot from 2023-04-22 16-51-04.png