Laravel file upload with vue.js & vite

Efficient file uploads are essential for modern web applications, enhancing user experience and functionality. In this tutorial, we'll build a robust Laravel file upload API with Vue.js integration, focusing on progress tracking and efficient handling using modern best practices.
By leveraging Vue.js for the front end and Laravel's robust back end, we will create a seamless file upload system complete with a progress bar and comprehensive validation.
Prerequisites
To follow along with this tutorial, ensure you have:
- Laravel 10.x or 11.x installed
- Vue.js 3.x
- Node.js 18.x or higher
- Composer
- Basic knowledge of Laravel and Vue.js
Setting up the laravel project
Create a new Laravel project using Composer:
composer create-project laravel/laravel file-upload-demo
cd file-upload-demo
Install the required dependencies:
composer install
npm install
For compiling front-end assets with Vite, run:
npm run dev
Set up your environment:
cp .env.example .env
php artisan key:generate
php artisan storage:link
Implementing a secure file upload API
Create a new controller for handling file uploads:
php artisan make:controller API/FileUploadController
Implement secure file handling in app/Http/Controllers/API/FileUploadController.php
:
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class FileUploadController extends Controller
{
public function upload(Request $request)
{
try {
$validated = $request->validate([
'file' => [
'required',
'file',
'max:10240',
'mimes:jpeg,png,pdf',
'mimetypes:image/jpeg,image/png,application/pdf'
]
]);
// For demonstration, files are stored locally.
// For production, consider using cloud storage such as Amazon S3:
// $path = Storage::disk('s3')->put('uploads', $request->file('file'));
$path = Storage::disk('public')->put('uploads', $request->file('file'));
return response()->json([
'success' => true,
'message' => 'File uploaded successfully',
'path' => $path,
'url' => Storage::disk('public')->url($path)
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => 'Upload failed: ' . $e->getMessage()
], 500);
}
}
}
Add the route with rate limiting in routes/api.php
:
Route::middleware(['throttle:uploads'])->group(function () {
Route::post('/upload', [App\Http\Controllers\API\FileUploadController::class, 'upload']);
});
Configure rate limiting in app/Providers/RouteServiceProvider.php
:
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::for('uploads', function (Request $request) {
return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip());
});
Integrating vue.js for front-end file management
Create a new Vue component using the Composition API.
Create resources/js/components/FileUpload.vue
:
<script setup>
import { ref } from 'vue'
import axios from 'axios'
const file = ref(null)
const progress = ref(0)
const message = ref('')
const uploading = ref(false)
const selectFile = (event) => {
file.value = event.target.files[0]
message.value = ''
}
const uploadFile = async () => {
if (!file.value) return
uploading.value = true
const formData = new FormData()
formData.append('file', file.value)
try {
const response = await axios.post('/api/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (event) => {
progress.value = Math.round((event.loaded * 100) / event.total)
},
})
message.value = response.data.message || 'File uploaded successfully'
file.value = null
} catch (error) {
message.value = `Upload failed: ${error.response?.data?.message || error.message}`
} finally {
uploading.value = false
progress.value = 0
}
}
</script>
<template>
<div class="upload-container">
<input
type="file"
@change="selectFile"
:disabled="uploading"
accept="image/jpeg,image/png,application/pdf"
/>
<button @click="uploadFile" :disabled="!file || uploading" class="upload-button">
{{ uploading ? 'Uploading...' : 'Upload' }}
</button>
<div v-if="progress > 0" class="progress-container">
<div class="progress-bar" :style="{ width: `${progress}%` }">%</div>
</div>
<div v-if="message" :class="['message', { error: message.includes('failed') }]">
{{ message }}
</div>
</div>
</template>
<style scoped>
.upload-container {
max-width: 500px;
margin: 20px auto;
padding: 20px;
}
.progress-container {
margin-top: 20px;
background: #f0f0f0;
border-radius: 4px;
overflow: hidden;
}
.progress-bar {
background: #4caf50;
color: white;
text-align: center;
padding: 4px;
transition: width 0.3s ease;
}
.message {
margin-top: 10px;
padding: 10px;
border-radius: 4px;
}
.message.error {
background: #ffebee;
color: #c62828;
}
.upload-button {
margin-top: 10px;
padding: 8px 16px;
background: #2196f3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.upload-button:disabled {
background: #ccc;
cursor: not-allowed;
}
</style>
Best practices for handling large file uploads
For production environments, implement these configurations:
- Update PHP settings in
php.ini
:
upload_max_filesize = 20M
post_max_size = 25M
max_execution_time = 300
- Configure Nginx (if using):
client_max_body_size 25M;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;
- Implement chunked uploads for large files using a package like
laravel-chunk-upload
.
Error handling and validation
Add a custom request class for validation:
php artisan make:request FileUploadRequest
Implement validation rules in app/Http/Requests/FileUploadRequest.php
:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class FileUploadRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'file' => [
'required',
'file',
'max:10240',
'mimes:jpeg,png,pdf',
'mimetypes:image/jpeg,image/png,application/pdf'
]
];
}
public function messages()
{
return [
'file.max' => 'The file size must not exceed 10MB.',
'file.mimes' => 'The file must be a JPEG, PNG, or PDF.',
];
}
}
Conclusion
We have built a robust file upload system using Laravel and Vue.js, implementing modern best practices for security, validation, and user experience. This solution features progress tracking, comprehensive error handling, and rate limiting to prevent abuse.
For more advanced features such as resumable uploads and cross-browser compatibility, consider using Uppy, an open-source file uploader that works seamlessly with Laravel.