in Code

I spent yesterday working on Image Cannon, an Express Node.js app I’m building to make it easier to share photos online. One feature it needs is Facebook photo sharing.

To upload photos with the Facebook Open Graph API, you can use either an existing link to an image on the internet or send the data as multipart/form-data. I don’t want to use the link method because this would require me to store the image somewhere on my server until it uploads, and it doesn’t let me test it out locally.

Unfortunately, most people seem to use the link method because it’s easier. Many also upload the photo client-side, but I needed a server-side solution. This presented some problems, as solutions for this relied on Javascript functions that aren’t present in Node.js. They were also usually pretty complicated.

To get started, I needed to upload an image. This is easy, using an HTML form with enctype="multipart/form-data".

Clicking submit submits a POST request to my /publish/facebookAccount route.

In that route is the code to upload to Facebook. I already authenticated with Facebook using Passport. I am using the request package to make HTTP requests. I’m not going to cover routes and Passport in this post.

This is the request code I tried first, and also where I got stuck for a while.

I kept getting the error: (#324) Requires upload file. I tried various different ways of converting it to base64, changing the headers of the request, manually creating the POST request in a string, and onwards.

But I wrongly assumed that the problem lay with the source: fs.createReadStream(req.file.path), when it was actually with the form-data I was supplying.

I found out by completely rewriting my code using a solution I found online.

This worked, and the only difference I could see was how the form-data was being created. So I decided to compare the two different form objects by console.loging them.

They’re pretty big, so I put them on Pastebin instead of sharing them here. The first one can be seen here and the second here. There are massive differences.

Now I had two questions:

  1. Why are two different form-datas created? What is the difference between the two methods of creating/appending to them?

  2. How does appending to the form-data from the request in method 1 work? When does the post request actually happen? I’d assume it does on request.post(..), but I can append stuff to the form-data before it gets posted, but after calling request.post(..).

I don’t post much to Stack Overflow, but I figured other people might have the same issues with photos uploading to Facebook and would look there for the answer. I also really, really wanted to know the answers to my own question.

The question was answered by mscdex. It turned out I misread the request documentation on forms. The first method I tried was sending requests as application/x-www-form-urlencoded, which doesn’t work because binary files, like images, can’t be sent through that. The form-data must be edited in a different way for it to be multipart/form-data, which does allow binary data to be sent.

The reason we can append data to the form after calling request.post(..) is because it is asynchronous. I thought this could be why, but figured attempting to add data to an asynchronous call before it completes is too risky to actually be the solution. Turns out I was right about it being asynchronous.

I still don’t trust it, so I rewrote the code using the documentation. It’s very similar to method 1, except that the form-data is passed in to a different argument. The first argument of request.post(..) is the main change.

Here it is in full.

Lesson learned: RTFM.

Leave a Reply