I was still tinkering with my pet project a little bit - connecting it to social media was an interesting learning experience as I had to create tiny service in Node.js due to fact Crystal lacked proper libraries (and that is ok, you don’t have to duplicate everything in every language). Below are some short notes that might help you a little bit if you’re trying to do similar thing.

Post multipart form in Crystal

This is easy part - crystal comes with very convenient FormData::Builder. So you don’t have to go crazy end base64 encode your file, put it in JSON payload and such - you can simply use native http way. Here is how you can use it:

io = IO::Memory.new

# boundary is optional - will be generated randomly if not specified
# unfortunately some frameworks in some languages
# have hardcoded boundary specified below in their multipart parsers!
# (they ignore Content-Type bounday=<boundary string> header) - worth keeping in mind
builder = HTTP::FormData::Builder.new(io, "AaB03x")

builder.field("myfield", "content of my field")

# optional headers for the file part
file_headers = HTTP::Headers{"Content-Type" => "image/jpeg"}
# `file` can be an IO; optional metadata
file_metadata = HTTP::FormData::FileMetadata.new(filename: File.basename(file.path))
builder.file("myimage", file, file_metadata, file_headers)
# you need to wrap everything up
builder.finish

# also don't forget to set proper request headers
# otherwise receiver will most likely fail to parse whole request
headers = HTTP::Headers{
  "Content-Type" => builder.content_type,
}

 HTTP::Client.post("http://my.endpoint", headers: headers, body: io.to_s) # => HTTP::Client::Response

Handle multipart in Express.js

Welcome to the world of javascript, world of deprecation.

Ok, jokes aside. It took me a while to select proper middleware, as Expressjs doesn’t ship with anything you can use for multipart by default. There is a bodyparser that does not support handle multipart bodies. There is also an article that suggests not using it, but I think it’s quite outdated as some of the facts are inconsistent with current state of things.

I ended up with multer that does the job just right and I think right now is the right choice for express. Anyhow, assuming you want to handle request submitted above you can do in your express app:

var express = require('express')
var multer  = require('multer')
var upload = multer({ dest: 'uploads/' })

var app = express()

app.post('/endpoint', upload.single('myimage'), function (req, res, next) {
  // req.file will hold `myimage` file (to access absolute path use req.file.path)
  // req.body will hold fields, so in our case you want to access req.body.myfield
})

And just that will do the job.


Note: Using Crystal 0.25.0 and expressjs 4.16.3.