063
LVL 03 — MID DEVELOPERSESSION 063DAY 63

FILE UPLOAD

🎫 PIXELCRAFT-050
Feature | 🟠 Hard | Priority: 🔴 Critical

Images need to reach the server. Implement file upload with progress tracking, server-side validation, and thumbnail generation.
CONCEPTS.UNLOCKED
📬
Multipart Form Data
How files travel over HTTP. Not JSON — binary data encoded as multipart/form-data. FormData API builds the request. The server parses the binary stream.
📦
Multer
Express middleware for handling file uploads. Parses multipart data, validates file type and size, saves to disk or memory. The standard Node.js upload solution.
🛡
Server-Side Validation
Check magic bytes, not just extension. A .exe renamed to .jpg still has executable magic bytes. Validate MIME type, file size, and dimensions on the server. Never trust the client.
💾
Storage Strategies
Local disk vs cloud storage. Local: simple, fast for dev. Cloud (S3, Cloudinary): scalable, CDN-backed, persistent. Start local, migrate to cloud when ready.
🖼
Thumbnail Generation
Sharp: create a small preview on upload. Original stays full resolution. Thumbnail (300×300) loads instantly in the gallery. Process images server-side for consistency.
📊
Upload Progress
XMLHttpRequest with progress events. Track bytes sent vs total bytes. Show a percentage progress bar. The user knows their upload is working.
HANDS-ON.TASKS
01
Configure Multer
npm install multer sharp const multer = require('multer'); const sharp = require('sharp'); const path = require('path'); const storage = multer.diskStorage({ destination: 'uploads/', filename: (req, file, cb) => { const uniqueName = `${Date.now()}-${Math.round( Math.random() * 1E9 )}${path.extname(file.originalname)}`; cb(null, uniqueName); }, }); const upload = multer({ storage, limits: { fileSize: 25 * 1024 * 1024 // 25MB max }, fileFilter: (req, file, cb) => { const allowedTypes = [ 'image/jpeg', 'image/png', 'image/webp' ]; if (allowedTypes.includes(file.mimetype)) { cb(null, true); } else { cb(new Error( 'Invalid file type. Only JPEG, ' + 'PNG, and WebP are allowed.' )); } }, });
02
Upload Endpoint with Thumbnail
app.post('/api/images/upload', authenticate, upload.single('image'), async (req, res) => { try { // Generate thumbnail const thumbPath = req.file.path .replace(/(\.[^.]+)$/, '_thumb$1'); await sharp(req.file.path) .resize(300, 300, { fit: 'inside' }) .toFile(thumbPath); const metadata = await sharp(req.file.path).metadata(); const image = await Image.create({ name: req.file.originalname, filename: req.file.filename, thumbnail: path.basename(thumbPath), width: metadata.width, height: metadata.height, fileSize: req.file.size, format: req.file.mimetype.split('/')[1], owner: req.userId, }); res.status(201).json(image); } catch (error) { res.status(500) .json({ error: 'Upload failed' }); } });
03
Frontend Upload with Progress
function uploadImage(file, onProgress) { return new Promise((resolve, reject) => { const formData = new FormData(); formData.append('image', file); 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 === 201) resolve(JSON.parse(xhr.responseText)); else reject(new Error('Upload failed')); }); xhr.open('POST', `${API_URL}/api/images/upload`); xhr.setRequestHeader('Authorization', `Bearer ${localStorage.getItem('token')}` ); xhr.send(formData); }); }
04
Build UploadZone Component

Build an <UploadZone> component with drag-and-drop and progress bar. Drag an image onto the page → see upload progress → thumbnail appears in gallery.

05
Close the Ticket
git switch -c feature/PIXELCRAFT-050-file-upload git add server/ src/ git commit -m "Add file upload with thumbnails and progress (PIXELCRAFT-050)" git push origin feature/PIXELCRAFT-050-file-upload # PR → Review → Merge → Close ticket ✅
CS.DEEP-DIVE

Streams and buffering.

A 50MB image doesn't load entirely into RAM. It's streamed in chunks.

// Streaming everywhere:

Video           → Netflix streams chunks,
                   not entire movies
Compression     → gzip streams
Database imports → read chunk, process,
                   next chunk
Log processing  → pipeline streams

// Understanding streams means you can
// process files of ANY size.
"Upload Lab"
[A]Add multi-file upload: select or drag multiple images, show individual progress bars for each, upload in parallel (Promise.all with concurrency limit of 3).
[B]Add magic bytes validation on the server: read the first 4 bytes of the file to verify it's actually an image, not a renamed executable. Compare file signature databases.
[C]Research cloud storage: sign up for Cloudinary free tier. Modify the upload endpoint to store images on Cloudinary instead of local disk. Compare: URL structure, CDN delivery, transformations.
REF.MATERIAL
ARTICLE
Express.js Team
Official Multer docs: storage engines, file filtering, limits, error handling, and multipart form data parsing.
MULTEROFFICIALESSENTIAL
ARTICLE
Lovell Fuller
High-performance image processing: resize, crop, format conversion, metadata extraction. The fastest Node.js image library.
SHARPOFFICIAL
VIDEO
Traversy Media
Complete file upload tutorial: Multer setup, validation, storage, and frontend integration with progress tracking.
UPLOADTUTORIAL
ARTICLE
Mozilla Developer Network
FormData API: constructing multipart requests, appending files, and sending binary data from the browser.
FORMDATAMDN
ARTICLE
Node.js Foundation
Stream fundamentals: readable, writable, transform, duplex. The theory behind efficient file processing.
STREAMSCSOFFICIAL
// LEAVE EXCITED BECAUSE
Drag an image onto the page → see upload progress → thumbnail appears in gallery. The full cloud workflow works. PixelCraft is a real web application.