Why build a websocket server?
When I started building the Void browser extension last year I quickly noticed that adding items from the extension while you have the web app open felt broken. I needed to add realtime sync to improve the UX of adding items from the browser extension.
Void’s API is built in Rails, so the obvious first-pass at websockets is to use ActionCable, however having used ActionCable in several other apps I’ve found it can be memory intensive, often bloating the core app process to over 1GB ram for a single puma worker. Memory usage is one of the biggest pains in deploying and scaling Ruby apps so I'd rather avoid that problem and continue to comfortably fit the Void API on a single 512MB Heroku dyno.
Another option I've used with great success is Pusher, it’s relatively simple, reliable and doesn’t cost much if you’re running a profitable app – again doesn’t quite fit Void’s needs, which is to be very cost-effective to run in the early stages of it’s life.
I was pretty sure at this point the best solution was to write a realtime microservice from scratch and being fairly proficient in Node.js (the metaphorical grandfather of websockets) I decided to pickup a language I haven't used before and write this service in Crystal.
After reading Mike Perham’s blog about choosing an alternate language for Sidekiq I wanted an excuse to try Crystal.
I build most apps in Ruby, I’ve helped build many companies on the back of Ruby programs and I still believe it’s one of the best languages and ecosystems out there to build applications with today. Anyway, I’ll save my Ruby ramblings for a future post, suffice to say Ruby is great for APIs and web apps, however handling many concurrent websocket connections might be more efficiently served by Crystal.
How'd it go? 🧐
Flawlessly, I think. Since initial deployment I haven’t had to make any changes or bugfixes, the only changes have been to get it ready for public release and to add a little
/info.json API endpoint to see how many clients are currently connected.
The language is nearly identical to Ruby despite being compiled and statically typed which is impressive and also makes it very easy for experienced Ruby developers to grok.
Getting into the nitty-gritty details under the hood Bifröst uses Kemal – a Sinatra inspired micro-framework, it’s super lightweight and should feel very familiar to anyone who’s used Sinatra before. Kemal has built-in support for websockets (as does Crystal) and thus made my job a breeze.
TDD is an imperative part of my workflow these days and any language inspired by Ruby is going to have a great testing story, Crystal ships with what is pretty much RSpec at the language level so I felt very at home. The one thing I haven’t figured out just yet is how to write tests for the web socket connections – if anyone reading this can help please leave a comment.
One last thing, I don’t feel I can give an intro to Crystal without mentioning Paul Smith of Thoughtbot who has been writing a web framework in Crystal called Lucky, I haven’t used it yet but it looks awesome and the premise of making nearly everything fail as a compile-time error is fantastic!
Thanks for reading and go check out Bifröst. If you have questions or problems please open an issue on Github.