169 lines
4.2 KiB
Markdown
169 lines
4.2 KiB
Markdown
---
|
|
title: Upload Files Correctly
|
|
impact: MEDIUM
|
|
impactDescription: Reliable uploads with progress tracking and validation
|
|
tags: files, upload, storage, attachments
|
|
---
|
|
|
|
## Upload Files Correctly
|
|
|
|
File uploads can use plain objects or FormData. Handle large files properly with progress tracking and appropriate error handling.
|
|
|
|
**Incorrect (naive file upload):**
|
|
|
|
```javascript
|
|
// Missing error handling
|
|
async function uploadFile(file) {
|
|
await pb.collection('documents').create({
|
|
title: file.name,
|
|
file: file
|
|
});
|
|
// No error handling, no progress feedback
|
|
}
|
|
|
|
// Uploading without validation
|
|
async function uploadAvatar(file) {
|
|
await pb.collection('users').update(userId, {
|
|
avatar: file // No size/type check - might fail server-side
|
|
});
|
|
}
|
|
|
|
// Base64 upload (inefficient)
|
|
async function uploadImage(base64) {
|
|
await pb.collection('images').create({
|
|
image: base64 // Wrong! PocketBase expects File/Blob
|
|
});
|
|
}
|
|
```
|
|
|
|
**Correct (proper file uploads):**
|
|
|
|
```javascript
|
|
// Basic upload with object (auto-converts to FormData)
|
|
async function uploadDocument(file, metadata) {
|
|
try {
|
|
const record = await pb.collection('documents').create({
|
|
title: metadata.title,
|
|
description: metadata.description,
|
|
file: file // File object from input
|
|
});
|
|
return record;
|
|
} catch (error) {
|
|
if (error.response?.data?.file) {
|
|
throw new Error(`File error: ${error.response.data.file.message}`);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Upload multiple files
|
|
async function uploadGallery(files, albumId) {
|
|
const record = await pb.collection('albums').update(albumId, {
|
|
images: files // Array of File objects
|
|
});
|
|
return record;
|
|
}
|
|
|
|
// FormData for more control
|
|
async function uploadWithProgress(file, onProgress) {
|
|
const formData = new FormData();
|
|
formData.append('title', file.name);
|
|
formData.append('file', file);
|
|
|
|
// Using fetch directly for progress (SDK doesn't expose progress)
|
|
return new Promise((resolve, reject) => {
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
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('Upload failed')));
|
|
|
|
xhr.open('POST', `${pb.baseURL}/api/collections/documents/records`);
|
|
xhr.setRequestHeader('Authorization', pb.authStore.token);
|
|
xhr.send(formData);
|
|
});
|
|
}
|
|
|
|
// Client-side validation before upload
|
|
function validateFile(file, options = {}) {
|
|
const {
|
|
maxSize = 10 * 1024 * 1024, // 10MB default
|
|
allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'],
|
|
maxNameLength = 100
|
|
} = options;
|
|
|
|
const errors = [];
|
|
|
|
if (file.size > maxSize) {
|
|
errors.push(`File too large. Max: ${maxSize / 1024 / 1024}MB`);
|
|
}
|
|
|
|
if (!allowedTypes.includes(file.type)) {
|
|
errors.push(`Invalid file type: ${file.type}`);
|
|
}
|
|
|
|
if (file.name.length > maxNameLength) {
|
|
errors.push(`Filename too long`);
|
|
}
|
|
|
|
return { valid: errors.length === 0, errors };
|
|
}
|
|
|
|
// Complete upload flow
|
|
async function handleFileUpload(inputEvent) {
|
|
const file = inputEvent.target.files[0];
|
|
if (!file) return;
|
|
|
|
// Validate
|
|
const validation = validateFile(file, {
|
|
maxSize: 5 * 1024 * 1024,
|
|
allowedTypes: ['image/jpeg', 'image/png']
|
|
});
|
|
|
|
if (!validation.valid) {
|
|
showError(validation.errors.join(', '));
|
|
return;
|
|
}
|
|
|
|
// Upload with progress
|
|
try {
|
|
setUploading(true);
|
|
const record = await uploadWithProgress(file, setProgress);
|
|
showSuccess('Upload complete!');
|
|
return record;
|
|
} catch (error) {
|
|
showError(error.message);
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Deleting files:**
|
|
|
|
```javascript
|
|
// Remove specific file(s) from record
|
|
await pb.collection('albums').update(albumId, {
|
|
'images-': ['filename1.jpg', 'filename2.jpg'] // Remove these files
|
|
});
|
|
|
|
// Clear all files
|
|
await pb.collection('documents').update(docId, {
|
|
file: null // Removes the file
|
|
});
|
|
```
|
|
|
|
Reference: [PocketBase File Upload](https://pocketbase.io/docs/files-handling/)
|