netipar/laravel-chunky
Chunk-based uploads for Laravel with resume support and an event-driven backend. Reliable large file uploads over unstable connections, plus framework-agnostic frontend clients for Vue 3, React, Alpine.js, and Livewire, with progress, batching, and broadcasting support.
Install the backend package via Composer:
composer require netipar/laravel-chunky
php artisan vendor:publish --tag=chunky-config
php artisan migrate
For frontend integration, install the framework-specific package (e.g., Vue 3):
npm install @netipar/chunky-vue3
First use case: Upload a single file with progress tracking in Vue 3:
<script setup>
import { useChunkUpload } from '@netipar/chunky-vue3';
const { upload, progress, isUploading } = useChunkUpload();
function handleFileChange(event) {
upload(event.target.files[0]);
}
</script>
<template>
<input type="file" @change="handleFileChange" />
<progress v-if="isUploading" :value="progress" max="100" />
</template>
Key next steps:
chunky.routes.middleware (e.g., ['auth:sanctum'])AssembleFileJobUploadCompleted in EventServiceProviderupload_id and returns chunking instructions.AssembleFileJob merges chunks to storage/app/chunky/uploads/{uploadId}.UploadCompleted, UploadFailed, etc.Example Event Listener:
// app/Listeners/ProcessUploadedFile.php
public function handle(UploadCompleted $event) {
$filePath = $event->getFinalPath();
// Process file (e.g., generate thumbnail, store metadata)
}
<script setup>
const { upload, progress, isUploading, error } = useChunkUpload({
maxConcurrent: 4, // Parallel chunks
autoRetry: true,
maxRetries: 3,
});
function handleUpload(file) {
upload(file, { folder: 'user_uploads' }); // Custom folder
}
</script>
const {
upload,
progress,
isUploading,
completedFiles,
totalFiles,
} = useBatchUpload({ maxConcurrentFiles: 2 });
function handleFiles(files) {
upload(files, { context: 'documents' }); // Group files
}
<livewire:chunky-upload context="profile_avatar" />
#[On('chunky-upload-completed')]
public function handleUpload(array $data) {
$this->emit('upload-success', $data['fileName']);
}
AssembleFileJob asynchronously (avoid sync driver):
php artisan queue:work
AssembleFileJob:
namespace App\Jobs;
use NETipar\Chunky\Jobs\AssembleFileJob as BaseJob;
class CustomAssembleJob extends BaseJob {
protected function assembleChunks() {
// Override logic (e.g., validate file type before assembly)
}
}
Use context to isolate uploads (e.g., by user or feature):
// Frontend
upload(file, { context: 'user_123_avatars' });
// Backend (config/chunky.php)
'contexts' => [
'user_*_avatars' => [
'disk' => 's3',
'folder' => 'avatars',
],
],
Configure per-context disks in chunky.php:
'disks' => [
's3' => 's3',
'local' => 'local',
],
Queue Stuck Jobs:
AssembleFileJob fails silently, check failed_jobs table.php artisan queue:retry.Lock Contention:
503 Service Unavailable during high traffic? Use Redis for chunky.lock_driver.chunky.lock_driver = 'redis' in config.Frontend Timeouts:
chunk_size in chunky.php (default: 5MB).CSRF Issues:
X-XSRF-TOKEN is sent.setDefaults({ headers: { 'X-CSRF-TOKEN': 'custom-token' } });
Memory Limits:
memory_limit or use chunky.staging_directory on a high-memory server.// config/chunky.php
'logging' => [
'enabled' => true,
'channel' => 'single',
],
php artisan chunky:status {uploadId}
php artisan chunky:cleanup --force
Custom Authorizer: Override ownership checks (e.g., for shared folders):
// app/Providers/ChunkyServiceProvider.php
$this->app->bind(
\NETipar\Chunky\Contracts\Authorizer::class,
\App\Services\CustomChunkyAuthorizer::class
);
Pre-Assembly Validation:
Extend AssembleFileJob to validate files before merging:
protected function assembleChunks() {
if (!in_array($this->fileName, ['jpg', 'png'])) {
throw new \Exception('Invalid file type');
}
parent::assembleChunks();
}
Broadcast Custom Data: Expose internal paths in broadcasts:
// config/chunky.php
'broadcasting' => [
'expose_internal_paths' => true,
],
Batch Uploads:
Limit max_files_per_batch to avoid DB bloat:
// config/chunky.php
'metadata' => [
'max_keys' => 100,
],
Chunk Size:
Tune chunk_size based on network stability (smaller = more resilient):
'chunk_size' => 2 * 1024 * 1024, // 2MB
Parallelism:
Adjust max_concurrent_chunks (default: 4) for faster uploads:
useChunkUpload({ maxConcurrent: 6 });
File Name Sanitization:
Frontend sends raw fileName; sanitize in UploadInitiated listener:
public function handle(UploadInitiated $event) {
$event->setFileName(strtolower($event->fileName));
}
Batch Limits: Prevent abuse by capping batch size:
// config/chunky.php
'batch' => [
'max_files' => 10,
],
Lock Expiry:
Short-lived locks may cause race conditions. Adjust lock_ttl:
'lock_ttl' => 300, // 5 minutes
How can I help you explore Laravel packages today?