Quest 135 - R2 Object Uploader
Quest 135: R2 Object Uploader
medium 25 minutes🎯 Learning Objectives
- เข้าใจ Cloudflare R2 object storage และ use cases
- รู้จัก presigned URLs สำหรับ secure file uploads
- เข้าใจ multipart upload strategy สำหรับไฟล์ขนาดใหญ่
- ตั้งค่า threshold สำหรับ single vs multipart upload
📖 Concept: R2 Object Storage & Upload Strategies
Cloudflare R2 เป็น S3-compatible object storage ที่ไม่มี egress fees — เหมาะสำหรับเก็บไฟล์, images, videos และ static assets ขนาดใหญ่
Presigned URLs คือ temporary URLs ที่ให้ permission upload/download ไฟล์ได้โดยไม่ต้อง expose credentials Think ของมันเหมือน “บัตรเข้าชมชั่วคราว” — ใช้ได้แค่ช่วงเวลาหนึ่งแล้วหมดอายุ
Multipart upload คือการแบ่งไฟล์ขนาดใหญ่เป็น chunks เล็กๆ แล้ว upload ทีละส่วน — เร็วกว่าและ reliable กว่า upload ทีเดียวสำหรับไฟล์ใหญ่
⚙️ How It Works
Upload Planning Flow
1. รับ fileName, fileSize, contentType ↓2. Validate inputs ↓3. ตรวจสอบ fileSize กับ MULTIPART_THRESHOLD (5MB) ↓4. ถ้า ≤ 5MB → single upload (สร้าง presigned URL) ↓5. ถ้า > 5MB → multipart upload (คำนวณจำนวน parts) ↓6. Return { strategy, presignedUrl/parts }Upload Strategy Decision
| File Size | Strategy | Details |
|---|---|---|
| ≤ 5MB | Single | สร้าง presigned URL สำหรับ upload เดียว |
| > 5MB | Multipart | แบ่งเป็น chunks 10MB ต่อ part |
Threshold Constants
const MULTIPART_THRESHOLD = 5 * 1024 * 1024; // 5MBconst CHUNK_SIZE = 10 * 1024 * 1024; // 10MB💡 Example: Planning Uploads
ขั้นตอนที่ 1: Validate inputs
if (!fileName || fileName.trim() === '') { errors.push('fileName is required');}if (typeof fileSize !== 'number' || fileSize <= 0) { errors.push('fileSize must be a positive number');}if (!contentType || contentType.trim() === '') { errors.push('contentType is required');}ขั้นตอนที่ 2: Single upload (small file)
// ไฟล์ 3MB → single uploadif (fileSize <= MULTIPART_THRESHOLD) { const presignedUrl = `https://r2.example.com/${fileName}?upload=${contentType}`; return { strategy: 'single', presignedUrl };}ขั้นตอนที่ 3: Multipart upload (large file)
// ไฟล์ 25MB → multipart (3 parts)const parts = Math.ceil(fileSize / CHUNK_SIZE);return { strategy: 'multipart', parts };ตัวอย่างผลลัพธ์:
planUpload('photo.jpg', 3 * 1024 * 1024, 'image/jpeg');// → { strategy: 'single', presignedUrl: 'https://r2.example.com/photo.jpg?upload=image/jpeg' }
planUpload('video.mp4', 25 * 1024 * 1024, 'video/mp4');// → { strategy: 'multipart', parts: 3 }
planUpload('', 1024, 'text/plain');// → { strategy: 'single', errors: ['fileName is required'] }⚠️ Common Mistakes
Mistake 1: ไม่ validate fileSize type
fileSize อาจเป็น string
"1024"แทน number1024→ ตรวจสอบtypeof fileSize === 'number'เสมอ
Mistake 2: ใช้ threshold ผิดหน่วย
ใช้ bytes แทน MB หรือสลับ MB กับ KB →
5 * 1024 * 1024= 5MB,10 * 1024 * 1024= 10MB
Mistake 3: ไม่ ceil จำนวน parts
25MB / 10MB = 2.5ต้องเป็น 3 parts ไม่ใช่ 2 → ใช้Math.ceil()เสมอสำหรับคำนวณจำนวน parts
Mistake 4: ลืม return error สำหรับ invalid inputs
ปล่อยผ่าน fileName ว่าง string → Validate ทุก input ก่อน return result
📝 Knowledge Check
📝 Knowledge Check
Q1:MULTIPART_THRESHOLD สำหรับ R2 upload คือเท่าใด?
Q2:ถ้าไฟล์มีขนาด 25MB และ CHUNK_SIZE = 10MB ต้องใช้กี่ parts?
Q3:Presigned URL ทำหน้าที่อะไร?
🏋️ Quest: R2 Object Uploader
ถึงเวลาสร้าง upload strategy planner แล้ว!
-
Download ไฟล์เริ่มต้นของ quest:
Terminal window npx bluebeltdojo download quest-135-r2-uploadercd quest-135-r2-uploader -
เปิด
problem.jsใน editor ของคุณพร้อมความช่วยเหลือของ AI -
Implement ฟังก์ชัน
planUpload(fileName, fileSize, contentType)ที่:- Validate inputs (fileName, fileSize, contentType)
- ตัดสินใจ single vs multipart ตาม file size
- สร้าง presigned URL สำหรับ single upload
- คำนวณจำนวน parts สำหรับ multipart upload
- Return
{ strategy, presignedUrl/parts, errors }
-
ตรวจสอบ solution ของคุณ:
Terminal window node test.js -
อ่าน test output อย่างละเอียด — ดูว่า test ไหน fail และทำไม
-
แก้ไขจนกว่า tests ทั้งหมดจะผ่าน
-
เมื่อ tests ผ่านทั้งหมด ส่งคำตอบ:
Terminal window npx bluebeltdojo submit
💡 Tip: ลองนึกถึง “ส่งพัสดุ” — ถ้ากล่องเล็กส่งทีเดียว, ถ้ากล่องใหญ่ต้องแยกเป็นหลายกล่อง
คำใบ้
- MULTIPART_THRESHOLD = 5MB (5 1024 1024 bytes)
- CHUNK_SIZE = 10MB (10 1024 1024 bytes)
- ใช้
Math.ceil()สำหรับคำนวณจำนวน parts - ตรวจสอบ
typeof fileSize === 'number'ก่อนคำนวณ