Author: Manoj Damor
When handling large file downloads in a Laravel application, it’s essential to provide feedback to users in real-time. With Laravel Reverb (WebSocket) and Laravel’s powerful HTTP Client, we can create a seamless user experience by showing download progress. This guide walks you through the full implementation.
Prerequisites
Before starting, ensure you have:
- Laravel 11+
- Laravel Reverb installed and running
- Laravel Echo and Pusher JS (for frontend broadcasting)
- A working Redis setup (default for broadcasting)
Step 1: Install and Start Laravel Reverb
Laravel Reverb is now bundled with Laravel 11+. To install:
php artisan install reverb
Start the WebSocket server:
php artisan reverb:start
Set the following in your .env file:
BROADCAST_DRIVER=pusher
PUSHER_APP_ID=reverb
PUSHER_APP_KEY=reverb
PUSHER_APP_SECRET=reverb
PUSHER_HOST=127.0.0.1
PUSHER_PORT=6001
PUSHER_SCHEME=http
Clear config cache:
php artisan config:clear
Step 2: Set Up Frontend with Laravel Echo
Install dependencies:
npm install --save laravel-echo pusher-js
Update resources/js/app.js:
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'reverb',
wsHost: window.location.hostname,
wsPort: 6001,
forceTLS: false,
disableStats: true,
});
Run the build:
npm run dev
Step 3: Create a Broadcast Event
Create a new event:
php artisan make:event DownloadProgressUpdated
Edit app/Events/DownloadProgressUpdated.php:
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;
class DownloadProgressUpdated implements ShouldBroadcast
{
use SerializesModels;
public $userId;
public $progress;
public function __construct($userId, $progress)
{
$this->userId = $userId;
$this->progress = $progress;
}
public function broadcastOn(): Channel
{
return new PrivateChannel('download-progress.' . $this->userId);
}
public function broadcastWith(): array
{
return ['progress' => $this->progress];
}
}
Step 4: Secure the Private Channel
Update routes/channels.php:
Broadcast::channel('download-progress.{userId}', function ($user, $userId) {
return (int) $user->id === (int) $userId;
});
Step 5: Create the File Download Logic
You can create a job or use a controller to handle downloads. Here’s a simple function that uses Laravel’s HTTP client:
use Illuminate\Support\Facades\Http;
use App\Events\DownloadProgressUpdated;
function downloadLargeFile($fileUrl, $savePath, $userId)
{
$resource = fopen($savePath, 'w');
Http::withOptions([
'progress' => function ($total, $downloaded) use ($userId) {
if ($total > 0) {
$percent = round(($downloaded / $total) * 100);
broadcast(new DownloadProgressUpdated($userId, $percent));
}
},
'sink' => $resource,
])->get($fileUrl);
fclose($resource);
}
You can call this function from a controller or dispatch it as a job for background processing.
Step 6: Show Progress on Frontend
In your Blade or Vue component:
<div id="progress">Download Progress: <span id="percentage">0%</span></div>
window.Echo.private(`download-progress.${userId}`)
.listen('DownloadProgressUpdated', (e) => {
document.getElementById("percentage").innerText = `${e.progress}%`;
});
Replace userId with the authenticated user’s ID in your Blade template:
<script>
let userId = {{ auth()->id() }};
</script>
Optional: Use Job for Background Downloads
You can offload the download to a queue:
php artisan make:job DownloadFileWithProgress
Inside the job, move the downloadLargeFile() logic.
Conclusion
Using Laravel Reverb and the built-in HTTP client, you can build a responsive download tracking system. With minimal setup, users can view real-time progress updates, leading to better UX and improved transparency in your application processes.
This solution can also be extended to monitor uploads, long-running reports, and any background tasks.
Author: Manoj Damor

Leave a Reply