Concepts
The file router, the presigned-upload lifecycle, adapters, and cleanup.
The file router
A file router is a typed map of named routes. Each route declares what it
accepts (files, maxFileSize, maxFileCount) and a builder chain
(.input(), .middleware(), .fields(), .onUploadComplete()). The router
type is exported once and consumed on the client, which is how the client hook
infers its own types — there is a single source of truth for every route's
shape. Name routes after what they carry — avatar, resume,
messageAttachment. See Type safety for that inference
shown end-to-end.
The presigned-upload lifecycle
Uploads never flow through your server. The browser uploads directly to S3 using a short-lived presigned URL. The server's job is to authorize, hand out the URL, and verify completion:
- Init — the client hook calls the server with the route and the files. The
server runs your middleware (auth, input parsing), creates pending file rows
through the database adapter, presigns
PUTURLs through the storage adapter, and returns them. - Upload — the client
PUTs the bytes directly to S3 using the presigned URLs. Your server never sees the file bytes. - Complete — the client tells the server the upload finished. The server
verifies the objects exist in storage, marks the rows stored, runs your
onUploadComplete, and returns its (typed) result to the client.
The two server round-trips are init (authorize + presign) and complete (verify + persist). Files that are initialized but never completed are left as pending rows — see Cleanup below.
Who may upload, and what "completed" proves, are security questions. The security model covers the trust boundaries, completion capability, upload window, and route-auth gotchas.
Adapters
upload-stuff talks to storage and to your database through two interfaces, so the core stays backend-agnostic:
- Storage adapter — presigns URLs, verifies objects, deletes objects. The
built-in
s3Adapterimplements it; theStorageAdapterinterface lets you target any object store. - Database adapter — creates pending file rows, marks them stored, finds
abandoned uploads, and deletes rows. The
prismaAdapteris a reference implementation for oneFilemodel; theDatabaseAdapterinterface lets you use any ORM.
See the API reference for both interfaces.
Cleanup
Because init creates a row before the client finishes uploading, an abandoned
upload leaves a pending row and (sometimes) no object. serverUtils.cleanUpFiles()
finds rows older than a threshold that were never completed and removes them. Run
it on a schedule (a cron job, a queue worker) to keep storage and the database in
sync.