
Upload Huge File - Mastering Chunked File Uploads in Laravel
In this tutorial, you'll learn how to efficiently upload large files using Laravel and Resumable.js, handling chunked file uploads and merging them on the server side.Uploading large files can be a challenge, but by breaking them into chunks, you can improve performance and user experience.
Why Use Chunked File Uploads?
When working with large files like videos or high-resolution images, uploading them in one go may lead to interruptions or failures due to network issues. Chunked file uploads break down large files into smaller parts (chunks), allowing for: * Resuming uploads after interruptions * Improved system performance * Efficient handling of large files This guide will show you how to implement chunked file uploads in Laravel using Resumable.js, a JavaScript library designed to handle large file uploads by splitting them into smaller parts.Prerequisites
If you haven’t installed Laravel yet, follow the Laravel installation instructions: Laravel Installation Documentation .
Step 1: Create a Controller for Chunked Uploads
To manage file uploads, we'll create a MediaController to handle both the chunked file uploads and the merging process on the server. Run the following command to generate the controller:php artisan make:controller MediaController
Step 2: Set Up the Blade View
Here’s a basic HTML view for uploading files. We'll use Resumable.js for chunked uploads and Bootstrap for styling the progress bar<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Upload Huge File - Elegant Laravel</title>
<!-- Resumable js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/resumable.js/1.0.3/resumable.min.js"
integrity="sha512-OmtdY/NUD+0FF4ebU+B5sszC7gAomj26TfyUUq6191kbbtBZx0RJNqcpGg5mouTvUh7NI0cbU9PStfRl8uE/rw=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- Bootstrap js -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
</head>
<body>
<div class="video_upload mt-5" id="video_upload ">
<div id="upload-container" class="text-center">
<button id="browseFile" class="btn btn-primary">Upload File</button>
</div>
<div style="display: none" class="progress mt-3 w-25 mx-auto" style="height: 25px">
<div class="progress-bar progress-bar-striped progress-bar-animated text-center" role="progressbar"
aria-valuenow="75" aria-valuemin="0" aria-valuemax="100" style="width: 75%; height: 100%">
75%</div>
</div>
</div>
<div class="d-none text-center" id="final_success">
<h2 class="text-success">File Upload Successfull </h2>
</div>
<!-- Botostrap -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.min.js"></script>
<!-- Jquery -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
</body>
</html>
Step 3: Set Up Routes for Chunked Uploads
Add the following routes in routes/web.php:use App\Http\Controllers\MediaController;
Route::get('/media', [MediaController::class,'create']);
Route::post('/media', [MediaController::class,'uploadMedia'])->name('media.upload');
Handle AJAX Request from Blade Template
let resumable = new Resumable({
target: "{{ route('media.upload') }}",
query: {
_token: '{{ csrf_token() }}'
},
chunkSize: 10 * 1024 * 1024, // 10MB chunks
testChunks: false,
throttleProgressCallbacks: 1,
});
resumable.assignBrowse(document.getElementById('browseFile'));
resumable.on('fileAdded', function(file) { // trigger when file picked
showProgress();
resumable.upload() // to actually start uploading.
});
resumable.on('fileProgress', function(file) { // trigger when file progress update
updateProgress(Math.floor(file.progress() * 100));
});
resumable.on('fileSuccess', function(file, response) { // trigger when file upload complete
$('#video_upload').addClass('d-none');
$('#video_upload').hide();
$('#final_success').removeClass('d-none');
$('#final_success').show();
hideProgress();
});
resumable.on('fileError', function(file, response) { // trigger when there is any error
hideProgress();
alert('file uploading error.')
});
let progress = $('.progress');
function showProgress() {
progress.find('.progress-bar').css('width', '0%');
progress.find('.progress-bar').html('0%');
progress.find('.progress-bar').removeClass('bg-success');
progress.show();
}
function updateProgress(value) {
progress.find('.progress-bar').css('width', `${value}%`)
progress.find('.progress-bar').html(`${value}%`)
}
function hideProgress() {
progress.hide();
}
Step 4: Handling File Chunks in the Controller
In MediaController, we’ll handle the chunked file uploads and merge them once all parts are uploaded.namespace App\Http\Controllers;
use Illuminate\Http\Request;
class MediaController extends Controller
{
public function create()
{
return view('media');
}
public function uploadMedia(Request $request)
{
$file = $request->file('file');
$chunkNumber = $request->input('resumableChunkNumber');
$totalChunks = $request->input('resumableTotalChunks');
$fileName = $request->input('resumableFilename');
$filePath = storage_path('app/uploads/' . $fileName . '.part');
// Move the uploaded chunk to the temporary directory
$file->move(storage_path('app/uploads'), $fileName . '.part' . $chunkNumber);
if ($chunkNumber == $totalChunks) {
$this->mergeChunks($fileName, $totalChunks);
}
return response()->json(['message' => 'Chunk uploaded successfully']);
}
private function mergeChunks($fileName, $totalChunks)
{
$filePath = storage_path('app/uploads/' . $fileName);
$output = fopen($filePath, 'wb');
for ($i = 1; $i <= $totalChunks; $i++) {
$chunkPath = storage_path('app/uploads/' . $fileName . '.part' . $i);
$chunkFile = fopen($chunkPath, 'rb');
stream_copy_to_stream($chunkFile, $output);
fclose($chunkFile);
unlink($chunkPath);
}
fclose($output);
}
}
Output
