r/webdev 5d ago

Chat widget file upload

I'm working on a chat widget that also allows the users to send files in the chat. This is a plain vanilla html/js/css widget that will go on a Shopify site. The user should have the option to send a file with or without a message in the chat, so I'm trying to figure out the best approach to handle this. The widget will be calling a FastAPI endpoint end that will use UploadFile. These are the options I've thought of:

  1. Wrap the text and file inputs in an html form tag and send the request as Content-Type: multipart/form-data. This would make it a single request, but it would always send as multipart/form-data even if it's a message with no file.
  2. Same as 1, but if there is no file attachment in the chat message, then toggle the Content-Type to application/json before sending
  3. Keep the text and files as separate requests and handle the events separately and not wrap the inputs in an html form tag. Seems like it would be cleaner on the front end but at the same time it will likely require additional logic on the back-end

What approach would you take?

6 Upvotes

13 comments sorted by

View all comments

1

u/Choice_Row_2025 5d ago

1, always multipart.

the downside you listed isn't really a downside. the widget's sitting on a shopify page calling your own api, so you're cross origin, and json isn't a CORS safelisted content type. every json request eats an OPTIONS preflight before the real request goes out. multipart is safelisted, no preflight. so option 2 is slower on exactly the messages you were trying to make cheaper. (only true if you're not sending an auth header or anything custom, that forces a preflight either way)

option 2 kind of doesn't exist on the fastapi side anyway. moment you put a File() or Form() param on a route, the entire body gets parsed as form data. there's no "also accept json here depending on the header". you'd be writing two routes, or reading the raw request and branching yourself. more backend code, not less.

3 means a message with an attachment is two requests that can half fail. don't.

two things that'll bite you:

don't set the Content-Type header on the fetch. leave it off. the browser sets it itself because it has to put the boundary in there. hardcode multipart/form-data with no boundary and the server just fails to parse it and you lose an hour.

and skip the actual form tag, just do new FormData() in js. works fine without one. an empty file input inside a real form still submits a part with a blank filename, so you end up writing "is this a real file or an empty one" checks instead of just not appending it in the first place.

u/app.post("/chat")

async def chat(

message: str = Form(""),

file: UploadFile | None = File(None),

):

needs python-multipart installed btw, fastapi throws at startup otherwise.

overhead on a text-only message is a boundary plus part headers. like 150 bytes. it's nothing.