How to upload an image using JavaScript?
Part 1: The upload protocol (with Nuxt and Express example)
A friend of mine went to a job interview as a web developer recently. One of the questions he got was "How do you save an image?"
This is a really good question to me, since I keep changing my mind on this topic and to be honest: I'm still unsure how to save an image "properly".
In this post, I want to explain my way of saving and displaying images with an example implementation in Nuxt and Express, answering the following:
- Which protocol to use?
- How to save (server side)
- How to display (client side)
- Additional Concerns (Authentication,...)
Which protocol to use?
The first question one should ask is how to get the image from the client side to the server. There are several options with drawbacks and benefits:
- multipart/form-data
- application/octet-stream or image/[image-type]
- application/json (encoded as string)
- application/x-www-form-urlencoded
- in websockets as chunked binary data
Let's eliminate the objectively "bad" options first, which would be "application/json" and "x-www-form-urlencoded". Both of these options can't handle binary data meaning the image has to be base64 (or one of the less common more efficient algorithms like base85 or base122) encoded. This is only acceptable in edge cases, like when you HAVE to use JSON for some specific reason, but generally not a good option to save an image.
We're left with 3 valid options:
multipart/form-data
This is a good option, but Multipart is a complex protocol. Multipart uploads support binaries (most important), uploading multiple files in one request, adding metadata to a file, upload progress tracking,... It is one of my favorite protocols used for uploading small files, however it also comes with some caveats, once you're trying to upload a really large file:
- it doesn't natively support chunking the uploaded data (splitting it in multiple requests, this is especially a problem once you have to deal with things like Cloudflare's 100MB Upload Body Size limit)
- it doesn't support resumable uploads out of the box and it's hard to implement given the protocol's complexity
application/octet-stream
This is basically just sending the binary directly to the server. This makes some things easier, like creating resumable file uploads. While it's probably the simplest method to use, I don't like it for the following reasons:
- it doesn't support adding any metadata, you have to create an extra endpoint (JSON or similar) where you can upload the metadata
- it doesn't support multiple files in a single request
in websockets as chunked binary data
This is a method I once implemented for a complex upload system which can support a lot of metadata, huge videos and so on via a custom protocol. The chunked binary data is temporarily saved as blob parts with byte tracking in the app's database. While this works surprisingly fine and supports resumable uploads in an efficient way, it also has its drawbacks:
- the implementation is complex and so prone to errors
- my solution needs to store the data temporarily in the database, causing a lot of database load when it's time to aggregate the chunks on upload finish
So basically all of our options are limited?
Yes, unfortunately this is the case...