In this article you will find out how to easily create a distributed websocket chat application with Crystal and Onyx Framework.

Crystal is a rapidly growing compiled language with speed of C and Ruby-inspired syntax. It has already proven its superiority regarding to websockets performance in Serdar Doğruyol’s blog post – it is able to handle up to 60000 concurrent connections on a 2 GB DigitalOcean droplet:

It works great in a single process, but what if you wanted to scale your application? It is very simple to do so with Onyx::HTTP channels and Onyx::EDA events.

Note that Onyx::EDA relies on Redis Streams feature, that’s why it requires Redis version >=5. I use wscat to test the websockets in the terminal in this article.

The repository is available at

Here goes the complete code of the server:

require "onyx/http"
require "onyx/eda/redis" # This requires REDIS_URL environment variable

struct Message
  include Onyx::EDA::Event

  getter username, content

  def initialize(@username : String, @content : String)

class Chat
  include Onyx::HTTP::Channel

  params do
    query do
      type username : String

  getter! sub : Onyx::EDA::Channel::Subscription(Message)

  def on_open
    @sub = Onyx::EDA.redis.subscribe(Message) do |message|
      socket.send("#{message.username}: #{message.content}")

  def on_message(message)
    Onyx::EDA.redis.emit(, message))

  def on_close
end "/", Chat
Onyx::HTTP.listen(port: ENV["PORT"].to_i) # You'll also need PORT variable to be set

First terminal:

> env PORT=5000 crystal src/
 INFO [21:25:38.483] ⬛ Onyx::HTTP::Server is listening at

Second terminal (note another port):

> env PORT=5001 crystal src/
 INFO [21:25:38.483] ⬛ Onyx::HTTP::Server is listening at

Third terminal:

> wscat --connect ws://localhost:5000?username=Alice
connected (press CTRL+C to quit)
> Hello! # Message sent from this terminal
< Alice: Hello!
< Bob: Hi!

Fourth terminal:

> wscat --connect ws://localhost:5001?username=Bob
connected (press CTRL+C to quit)
< Alice: Hello!
> Hi! # Message sent from this terminal
< Bob: Hi!

These are two separate websocket chat processes which use Redis as a back-end for synchronisation, in just in 40 lines of code!

Testing is good. And it is also simple with Onyx. This code would ensure everything is working as intended:

describe "server" do
  alice_socket ="/?username=Alice")
  bob_socket ="/?username=Bob")

  it do
    alice_socket.assert_response("Bob: Hello")

  it do
    bob_socket.assert_response("Alice: Hi")

Crystal dependencies (shards) you’d need:

    github: onyxframework/onyx
    version: ~> 0.4.0
    github: onyxframework/http
    version: ~> 0.8.0
    github: onyxframework/eda
    version: ~> 0.3.0

The whole source code is available at Enjoy!

If you like this article but have no experience in Crystal yet, then follow out series of tutorials which would lead you through the basics.