Skip to content
← Work index

04Case study

Uploadery

Cloud file hosting with signed delivery

Year
2025
Role
Full-stack
Status
Live

The obvious way to build file hosting is to accept the upload in your own handler and forward it on. It works until the first large file, at which point your web server is a very expensive, very slow pipe. Uploadery never touches the bytes.

0
Bytes through the app server
Pre-signed
Both directions of transfer
Metadata
All the database ever holds

The constraint

What made it hard

Proxying uploads through the application means every concurrent transfer occupies a request handler for its full duration, buffers into memory or a temp disk, and runs against the platform’s request body limits and timeouts. Egress is paid twice - once into the server, once out of it. The failure mode is unpleasant too: the connection dies mid-file and there is a half-written object and no clean way to resume.

Approach

  1. 01

    The server issues permission, not bandwidth

    A client asks for an upload slot; the application authenticates it, decides whether that user may write, and returns a short-lived pre-signed S3 URL. The browser then talks to S3 directly. The application has done its job before a single byte moves.

  2. 02

    Reads are signed the same way

    Downloads are short-expiry signed GETs rather than public objects. Access control lives in the decision to issue a URL, so a leaked link stops working on its own instead of exposing a permanently public bucket path.

  3. 03

    Derivatives generated on ingest

    Images are optimised and resized once, on arrival, and the variants are stored alongside the original - so serving a thumbnail is a static fetch rather than a transformation on every request.

  4. 04

    The database describes files, it does not store them

    Prisma models hold owner, key, size, content type and timestamps. That keeps rows small and queries fast, and means a storage migration is a key rewrite rather than a data export.

Architecture

Uploadery pre-signed transfer pathBrowserholds the fileApplicationissues permissionS3holds the bytesPostgreSQLprisma1 · ask for a url2 · signed url back3 · the bytes move straight between browser and bucketnever touches the fileowner, key, size, typethe server decides who may transfer; it is not in the path of the transfer
The application sits outside the transfer. It authorises, then steps back - the accent path never passes through it.

Specification

Transfer
Pre-signed S3 PUT and GET, short expiry
Auth
NextAuth sessions gate URL issuance
Storage
AWS S3, private bucket, no public objects
Metadata
PostgreSQL via Prisma - owner, key, size, type
Images
Optimised variants generated once on ingest

Stack

  • Next.js
  • React
  • Prisma
  • PostgreSQL
  • AWS S3
  • NextAuth
  • Tailwind CSS

Where it landed

  • 01

    Large uploads are limited by the user’s connection, not by a request timeout.

  • 02

    Hosting cost scales with storage rather than with server time spent shovelling bytes.

  • 03

    A shared link expires by itself; nothing in the bucket is ever publicly readable.

Next case study

05

Paywallio

Content monetisation and gated access

Read →

Something that has towork in production?

I take on contract engineering and full-time roles. Most useful first message: what you are building, what is currently in the way, and when you need it live.