← Back to case studies

· 2 min read

Uploading 150 MB Audio Files to S3 Without Killing the Server

A direct-to-S3 multipart upload pipeline for large audio — presigned URLs, client-driven parts, and a worker that finalizes assembly. Zero bytes touch the app server.

Problem

Users upload long audio files (100–300 MB). Streaming them through an Express server OOMs under concurrency and ties up the event loop. We needed large uploads to go straight to object storage.

Result

Server memory flat (~120 MB) regardless of file size; ~300 MB uploads in under a minute on a 1-core box; resumable after drops.

Stack

  • TypeScript
  • Express
  • React
  • S3/R2
  • AWS SDK v3
  • Zod

TL;DR

ApproachServer memoryResumableVerdict
Stream through ExpressSpikes, OOMs under loadNo
Client → S3 (presigned multipart)Flat (signs + finalizes only)Yes

Architecture

Browser → S3 directly, with the app server only signing requests and finalizing.

Browser                  Express API                S3 / R2
  │  1. request upload      │
  │ ─────────────────────▶ │
  │  2. presigned multipart │
  │ ◀───────────────────── │
  │                                                    │
  │  3. PUT part 1…N (presigned)  ────────────────────▶│
  │                                                    │
  │  4. complete multipart → API                       │
  │ ─────────────────────▶ │  5. CompleteMultipartUpload
  │                        │ ─────────────────────────▶│
  │  6. ok + record id     │
  │ ◀───────────────────── │

Interesting decisions

Lessons learned

Pros & cons

Pros: server memory flat regardless of file size; resumable; S3/R2 parity. Cons: more moving parts on the client; you must handle the abort lifecycle.

Work with me

I build business software like this — fast, with AI-assisted delivery. Open to remote (US/EU) roles and freelance.

Get in touch · See my AI workflow →