Building a Full-Stack Large File Uploader with Chunking, Instant Upload, and Resume
Handling multi-gigabyte uploads reliably is a recurring pain point in web applications, and off-the-shelf libraries often obscure the control flow needed for production features like pause, resume, and deduplication. This implementation exposes the exact decisions — chunk sizing, hash sampling tradeoffs, memory-efficient merging, and concurrency limits — that turn a brittle upload into a resilient pipeline.
A complete walkthrough builds a large file upload system from the ground up, starting with the browser primitives Blob, File, and ArrayBuffer. The frontend slices files into 5MB chunks, computes an MD5 fingerprint with SparkMD5 (including a sampling shortcut for files over 100MB), and controls concurrency with p-limit. The Express backend receives chunks into memory via Multer, writes them to hash-named directories, and merges them sequentially using a write stream to keep memory constant regardless of file size. On top of this pipeline, two optimizations are layered in: an instant-upload check that skips transmission when the server already holds the file, and a resumable-upload mechanism that queries which chunks are missing and retransmits only those, using AbortController to cancel in-flight requests on pause.
Hash sampling is a pragmatic tradeoff that many tutorials skip; the 8-sample ceiling plus file-size salt is a concrete, repeatable heuristic that keeps the fingerprint fast even for multi-GB files.
The design choice to avoid a database and rely solely on filesystem naming conventions (hash-prefixed filenames, chunk-indexed directories) keeps the implementation self-contained and easy to reason about, though it limits multi-server scale.
Placing the merge logic's instant-upload fallback inside the merge endpoint itself is a clever concurrency safeguard: if two users race to upload the same file, the second merge call detects the already-written file and cleans up without corrupting data.