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.
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:
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); }}
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