Capstone: File Sharing API
What we built
A production-grade file sharing API with every upload pattern:
| Feature | What it does | Key files |
|---|---|---|
| Multipart parsing | Stream files from HTTP to busboy | upload.ts |
| Validation | Size limits, MIME type via magic bytes, filename sanitization | upload.ts |
| Local storage | Save to disk with UUID names | providers/local.ts |
| Serving | Stream from disk with correct headers | download route |
| Range requests | Partial content, video seeking, resumable downloads | download route |
| Access control | Auth check + signed URLs | access-control.ts |
| Streaming | Pipe directly to disk, constant memory | streaming upload |
| Thumbnails | Resize and crop with Sharp | image.ts |
| Progress | Track bytes, SSE progress stream | progress route |
| Presigned URLs | Upload directly to S3/R2 | cloud-storage.ts |
| Storage abstraction | Swap local for S3 via config | storage-interface.ts |
The complete route map
Upload
POST /files → upload via multipart form data
POST /files/upload-url → get presigned URL for direct-to-cloud upload
POST /files/confirm → confirm cloud upload
Download
GET /files/:id/download → download (with range request support)
GET /files/:id/view → view inline (images, PDFs)
GET /files/:id/thumbnail → thumbnail (200×200)
GET /files/:id/signed → download via signed URL (no auth needed)
Management
GET /files → list user's files
GET /files/:id → file metadata + signed download URL
DELETE /files/:id → delete file + cleanup from storage
Progress
GET /uploads/:id/progress → SSE stream of upload progress Test the complete system
npm run dev
# === Upload ===
curl -X POST http://localhost:3000/files \
-F "[email protected]"
# { "id": "...", "name": "photo.jpg", "mimeType": "image/jpeg", "size": 12345, "url": "/files/..." }
# === Download ===
curl -o downloaded.jpg http://localhost:3000/files/FILE_ID/download
# === View inline ===
# Open in browser: http://localhost:3000/files/FILE_ID/view
# === Thumbnail ===
curl -o thumb.jpg http://localhost:3000/files/FILE_ID/thumbnail
# === Range request (first 1 KB) ===
curl -H "Range: bytes=0-1023" http://localhost:3000/files/FILE_ID/download
# === Signed URL ===
curl http://localhost:3000/files/FILE_ID
# Response includes downloadUrl with signature
curl -o file.jpg "http://localhost:3000/files/signed/STORED_NAME?expires=...&sig=..."
# === List files ===
curl http://localhost:3000/files
# === Delete ===
curl -X DELETE http://localhost:3000/files/FILE_ID
# === Validation ===
# Upload a file too large
dd if=/dev/zero of=big.bin bs=1M count=20
curl -X POST http://localhost:3000/files -F "[email protected]"
# 413: File exceeds size limit
# Upload a disallowed type
curl -X POST http://localhost:3000/files -F "[email protected]"
# 400: File type not allowed Project structure
src/
app.ts # Hectoday HTTP setup, all routes
server.ts # HTTP server
db.ts # Schema, metadata storage
upload.ts # Multipart parsing, streaming
image.ts # Thumbnail generation (Sharp)
storage-interface.ts # StorageProvider interface
providers/
local.ts # Local disk storage
s3.ts # S3/R2 storage
storage.ts # Provider selection
routes/
files.ts # Upload, download, list, delete
progress.ts # SSE upload progress The security layers
Every upload passes through multiple checks:
Request arrives
│
├─ Content-Type: multipart/form-data? → 400 if not
├─ File size within limit? → 413 if not (streaming check)
├─ MIME type allowed? (magic bytes) → 400 if not
├─ Filename sanitized? → UUID replaces original
├─ File saved to isolated directory → not in web root
│
└─ Metadata recorded in database Every download passes through access checks:
Request arrives
│
├─ Signed URL? → verify signature and expiry
├─ Authenticated? → verify session
├─ Authorized? → file owner or public file
│
├─ Range request? → serve partial content (206)
└─ Full request? → serve complete file (200) Challenges
Challenge 1: Add virus scanning. Integrate ClamAV to scan uploaded files. Reject files that contain malware. Quarantine suspicious files for manual review.
Challenge 2: Add chunked uploads. Split large files into chunks on the client. Upload each chunk separately. Reassemble on the server. This enables resumable uploads for very large files.
Challenge 3: Add file versioning. Keep previous versions when a file is updated. Allow downloading old versions. Show version history.
Challenge 4: Add shared file links. Generate a shareable link that lets anyone with the link download the file (like Google Drive’s “anyone with the link” sharing).
What is the most important security measure for file uploads?
When should you use presigned URLs instead of server-side uploads?