Розробка форми з завантаженням файлів на сайті
Форма з завантаженням файлів — один із тих компонентів, де користувацький досвід та надійність бекенду одинаково важливі. Погана реалізація ломається на великих файлах, не дає зворотного зв'язку та втрачає дані при помилках мережі. Правильна — працює в будь-яких умовах.
Що входить у реалізацію
Клієнтська частина:
- Drag-and-drop зона + кнопка "Вибрати файл"
- Превью для зображень (через
FileReaderабоURL.createObjectURL) - Прогрес-бар завантаження з реальними відсотками
- Валідація: тип файлу, розмір, кількість
- Обробка помилок з людськими повідомленнями
- Скасування завантаження через
AbortController
Серверна частина:
- Multipart upload з підтримкою великих файлів (chunked upload при необхідності)
- Валідація MIME-типу за вмістом файлу, а не тільки по розширенню
- Антивірусна перевірка через ClamAV або сторонній API (опціонально)
- Зберігання: локально, S3-сумісне сховище (MinIO, AWS S3, Cloudflare R2)
- Генерація унікальних імен файлів, ізоляція по користувачам
Технічний стек
| Рівень | Варіанти |
|---|---|
| UI-компонент | React + react-dropzone, Vue + власний гук |
| HTTP-завантаження | XMLHttpRequest (прогрес), fetch + ReadableStream |
| Бекенд | Laravel (Storage facade), Node.js (multer, busboy) |
| Сховище | AWS S3, MinIO, локальний диск |
| Превью зображень | Canvas API, sharp на сервері |
Приклад: базова завантаження з прогресом
function uploadFile(file, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('file', file);
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
onProgress(Math.round((e.loaded / e.total) * 100));
}
});
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(`Upload failed: ${xhr.status}`));
}
});
xhr.addEventListener('error', () => reject(new Error('Network error')));
xhr.open('POST', '/api/upload');
xhr.setRequestHeader('X-CSRF-TOKEN', document.querySelector('meta[name="csrf-token"]').content);
xhr.send(formData);
});
}
Chunked upload для великих файлів
Для файлів від 100 МБ та вище — використовуємо розбивку на частини. Стандарт де-факто — tus protocol, для S3 — Multipart Upload API.
// tus-js-client
import { Upload } from 'tus-js-client';
const upload = new Upload(file, {
endpoint: '/api/upload/tus',
chunkSize: 5 * 1024 * 1024, // 5 МБ частини
retryDelays: [0, 1000, 3000, 5000],
metadata: { filename: file.name, filetype: file.type },
onProgress(bytesUploaded, bytesTotal) {
const pct = ((bytesUploaded / bytesTotal) * 100).toFixed(1);
console.log(`${pct}%`);
},
onSuccess() {
console.log('Готово:', upload.url);
},
});
upload.start();
На сервері Laravel — пакет ankurk91/laravel-tus-upload або власна реалізація через tus-php.
Валідація на сервері (Laravel)
$request->validate([
'file' => [
'required',
'file',
'max:102400', // 100 МБ
'mimes:jpg,jpeg,png,pdf,docx',
function ($attribute, $value, $fail) {
$mime = mime_content_type($value->getRealPath());
$allowed = ['image/jpeg', 'image/png', 'application/pdf'];
if (!in_array($mime, $allowed)) {
$fail('Тип файлу не дозволений.');
}
},
],
]);
Безпека
-
Ніколи не довіряти
$_FILES['type']— тількиmime_content_type()абоfinfo - Зберігати файли поза
public/або у окремому S3 bucket без публічного доступу - Роздавати файли через підписані URLs (S3 Presigned URLs) з TTL
- Обмежувати rate limiting на ендпоінт завантаження
- Сканувати архіви (zip bomb protection): перевіряти коефіцієнт стиснення
Терміни
Базова форма з drag-and-drop, прогресом та S3-сховищем — 3–4 робочих дні. Chunked upload з можливістю поновлення, антивірусною перевіркою та адміністративним інтерфейсом управління файлами — 7–10 днів.







