hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses File Uploads and Storage with @hectoday/http

The Basics

  • Why File Uploads Are Hard
  • Project Setup

Receiving Files

  • Multipart Form Data
  • Validating Uploads
  • Saving to Disk

Serving Files

  • Serving Static Files
  • Range Requests and Resumable Downloads
  • Access Control on Files

Production Patterns

  • Streaming Uploads
  • Image Processing
  • Upload Progress and Cancellation

Cloud Storage

  • Presigned URLs
  • Moving from Local to Cloud

Putting It All Together

  • File Upload Checklist
  • Capstone: File Sharing API

Capstone: File Sharing API

What we built

A production-grade file sharing API with every upload pattern:

FeatureWhat it doesKey files
Multipart parsingStream files from HTTP to busboyupload.ts
ValidationSize limits, MIME type via magic bytes, filename sanitizationupload.ts
Local storageSave to disk with UUID namesproviders/local.ts
ServingStream from disk with correct headersdownload route
Range requestsPartial content, video seeking, resumable downloadsdownload route
Access controlAuth check + signed URLsaccess-control.ts
StreamingPipe directly to disk, constant memorystreaming upload
ThumbnailsResize and crop with Sharpimage.ts
ProgressTrack bytes, SSE progress streamprogress route
Presigned URLsUpload directly to S3/R2cloud-storage.ts
Storage abstractionSwap local for S3 via configstorage-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?

← File Upload Checklist Back to course →

© 2026 hectoday. All rights reserved.