Laravel Backend (API route + controller)
routes/api.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\FileUploadController;
Route::post('/upload', [FileUploadController::class, 'upload']);
app/Http/Controllers/FileUploadController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class FileUploadController extends Controller
{
public function upload(Request $request)
{
$request->validate([
'file' => 'required|file|max:10240', // max 10MB
]);
if ($request->hasFile('file')) {
$path = $request->file('file')->store('uploads', 'public');
return response()->json(['message' => 'File uploaded successfully', 'path' => $path]);
}
return response()->json(['message' => 'No file uploaded'], 400);
}
}
Make sure your storage:link
is set up:
php artisan storage:link
Vue.js Frontend Component (with progress bar)
Vue Component: FileUploader.vue
<template>
<div class="max-w-md mx-auto p-4 border rounded-xl bg-white shadow">
<h2 class="text-xl font-semibold mb-4">Upload File</h2>
<input type="file" @change="handleFileChange" class="mb-4" />
<button
:disabled="!file || isUploading"
@click="uploadFile"
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 disabled:opacity-50"
>
{{ isUploading ? 'Uploading...' : 'Upload' }}
</button>
<div v-if="isUploading" class="mt-4 w-full bg-gray-200 rounded-full h-4 overflow-hidden">
<div
class="bg-blue-500 h-full transition-all duration-300"
:style="{ width: progress + '%' }"
></div>
</div>
<p v-if="uploadMessage" class="mt-4 text-green-600 font-medium">{{ uploadMessage }}</p>
<p v-if="errorMessage" class="mt-4 text-red-600 font-medium">{{ errorMessage }}</p>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "FileUploader",
data() {
return {
file: null,
progress: 0,
isUploading: false,
uploadMessage: "",
errorMessage: ""
};
},
methods: {
handleFileChange(e) {
this.file = e.target.files[0];
this.uploadMessage = "";
this.errorMessage = "";
},
uploadFile() {
if (!this.file) return;
const formData = new FormData();
formData.append("file", this.file);
this.isUploading = true;
this.progress = 0;
axios.post("/api/upload", formData, {
headers: {
"Content-Type": "multipart/form-data"
},
onUploadProgress: (progressEvent) => {
this.progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
}
})
.then((res) => {
this.uploadMessage = res.data.message;
this.errorMessage = "";
})
.catch((err) => {
this.errorMessage = err.response?.data?.message || "Upload failed.";
this.uploadMessage = "";
})
.finally(() => {
this.isUploading = false;
});
}
}
};
</script>
Mount Vue App (example)
resources/js/app.js
import { createApp } from 'vue'
import FileUploader from './components/FileUploader.vue'
const app = createApp({})
app.component('file-uploader', FileUploader)
app.mount('#app')
resources/views/welcome.blade.php
<div id="app">
<file-uploader></file-uploader>
</div>