You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I've been tracking #1937 for a while, and recently noticed the TRPC dev team is adding experimental support for file uploads using multipart/form-data requests. That's great news!
However, I personally really dislike multipart/form-data. It's so complex, and IME it's nothing but a source of wasted time and brain power. I'd like to suggest an alternative: application/octet-stream, i.e. setting the request body to the file content.
Client
On the client side, it's just:
fetch(url,{method: "POST",body: file})
where file is a File. The body is simply the content of the file. Every HTTP client (JS or not) natively supports streaming request bodies because it's so trivial: just dump a stream of bytes into the socket. The same can't be said for multipart requests.
Need extra information (like filename)? Just stick it in the query parameters. This means you can use the exact same validation schema/strategy as everywhere else, instead of needing to come up with multipart-specific validation. I admit using query parameters in POST requests is not conventional, but to me it's a lesser evil.
Ultimately, this gives you a simple HTTP request like this:
POST ${url}
Content-Length: ${file.size}
Content-Type: ${file.type}
<FILE CONTENT>
Server
The server is where it gets even more convenient. Here's a vanilla nodejs handler for the request above (validation of things like req.method and Content-Type omitted for the sake of brevity), which returns the md5 of the file:
importcryptofrom"crypto";importhttpfrom"http";import{pipeline}from"stream/promises";http.createServer(async(req,res)=>{// Use "regular" validation for query params!consthasher=crypto.createHash("md5");awaitpipeline(req,hasher);constmd5=hasher.digest("hex");res.statusCode=200;res.write(md5);res.end();}).listen(3333);
req is a stream containing the body (the file content). Note that there's no temporary files or in-memory buffering. This is also the most performant approach!
I imagine the typical use-case for file uploads is to upload it to an S3 bucket or Azure storage container. We use Azure at $DAYJOB, and here's what the handler looks like:
That's it. Again, this is the most performant approach.
Text data
As mentioned previously, query parameters can be used for text data. However, if the file is part of a larger form, then you might find it odd to send all the other inputs in query parameters. That's a feature, not a bug! If the request might fail due to validation errors on text fields, and for instance those fields can't be fully validated client-side because you need check for uniqueness, then I'd argue you're probably better off uploading the file and submitting the form in two separate requests.
Yes this complicates things a bit, but I think it leads to better UX. Although TBH I still find that less complicated than handling multipart requests.
This is also what you end up doing anyway if you do direct-to-S3 uploads (maybe via uploadthing).
Caveats
I see two caveats:
Can only be used for uploading a single file at a time.
Not a big downside IMO. In fact, if you have several large files to upload, I believe it's best to upload them separately anyway (better retries).
Not "browser-native".
If you're using TRPC, I think you're pretty far from that anyway.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
I've been tracking #1937 for a while, and recently noticed the TRPC dev team is adding experimental support for file uploads using multipart/form-data requests. That's great news!
However, I personally really dislike
multipart/form-data
. It's so complex, and IME it's nothing but a source of wasted time and brain power. I'd like to suggest an alternative:application/octet-stream
, i.e. setting the request body to the file content.Client
On the client side, it's just:
where
file
is a File. The body is simply the content of the file. Every HTTP client (JS or not) natively supports streaming request bodies because it's so trivial: just dump a stream of bytes into the socket. The same can't be said for multipart requests.Need extra information (like filename)? Just stick it in the query parameters. This means you can use the exact same validation schema/strategy as everywhere else, instead of needing to come up with multipart-specific validation. I admit using query parameters in
POST
requests is not conventional, but to me it's a lesser evil.Ultimately, this gives you a simple HTTP request like this:
Server
The server is where it gets even more convenient. Here's a vanilla nodejs handler for the request above (validation of things like
req.method
andContent-Type
omitted for the sake of brevity), which returns the md5 of the file:req
is a stream containing the body (the file content). Note that there's no temporary files or in-memory buffering. This is also the most performant approach!I imagine the typical use-case for file uploads is to upload it to an S3 bucket or Azure storage container. We use Azure at $DAYJOB, and here's what the handler looks like:
That's it. Again, this is the most performant approach.
Text data
As mentioned previously, query parameters can be used for text data. However, if the file is part of a larger form, then you might find it odd to send all the other inputs in query parameters. That's a feature, not a bug! If the request might fail due to validation errors on text fields, and for instance those fields can't be fully validated client-side because you need check for uniqueness, then I'd argue you're probably better off uploading the file and submitting the form in two separate requests.
Yes this complicates things a bit, but I think it leads to better UX. Although TBH I still find that less complicated than handling multipart requests.
This is also what you end up doing anyway if you do direct-to-S3 uploads (maybe via uploadthing).
Caveats
I see two caveats:
Beta Was this translation helpful? Give feedback.
All reactions