It's like ActionCable (100% compatible with JS Client), but you know, for Crystal


Build Status

It's like ActionCable (100% compatible with JS Client), but you know, for Crystal


  1. Add the dependency to your shard.yml:

        github: cable-cr/cable
  2. Run shards install


require "cable"

With Lucky

On your src/ add the Cable::Handler before Lucky::RouteHandler

class AppServer < Lucky::BaseAppServer
  def middleware

After that, you can configure your Cable, the defaults are:

Cable.configure do |settings|
  settings.route = "/cable"    # the URL your JS Client will connect
  settings.token = "token"     # The query string parameter used to get the token

Then you need to implement a few classes

The connection class is how you are gonna handle connections, it's referenced on the src/ when creating the handler.

module ApplicationCable
  class Connection < Cable::Connection
    # You need to specify how you identify the class, using something like:
    # Remembering that it must, be a String
    # Tip: Use your `User#id` converted to String
    identified_by :identifier

    # If you'd like to keep a `User` instance together with the Connection, so
    # there's no need to fetch from the database all the time, you can use the
    # `owned_by` instruction
    owned_by current_user : User

    def connect
      # Implement your Auth logic, something like
      payload = JWT.decode(token, Lucky::Server.settings.secret_key_base, JWT::Algorithm::HS256)
      self.identifier = payload["id"].to_s
      self.current_user = UserQuery.find(payload["id"])
    rescue e : Avram::RecordNotFoundError

Then you need your base channel, just to make easy to aggregate your app's cables logic

module ApplicationCable
  class Channel < Cable::Channel

Then create your cables, as much as your want!! Let's setup a ChatChannel as example:

class ChatChannel < ApplicationCable::Channel
  def subscribed
    # We don't support stream_for, needs to generate your own unique string
    stream_from "chat_#{params["room"]}"

  def receive(data)
    broadcast_message = {} of String => String
    broadcast_message["message"] = data["message"].to_s
    broadcast_message["current_user_id"] = connection.identifier
    ChatChannel.broadcast_to("chat_#{params["room"]}", broadcast_message)

  def perform(action, action_params)
    user =
    user.away if action == "away"
    user.status(action_params["status"]) if action == "status"
    ChatChannel.broadcast_to("chat_#{params["room"]}", {
      "user"      =>,
      "performed" => action.to_s,

  def unsubscribed
    # You can do any action after client closes connection
    user =

Check below on the JavaScript section how to communicate with the Cable backend


It works with ActionCable JS Client out-of-the-box!! Yeah, that's really cool no? If you need to adapt, make a hack, or something like that?! No, you don't need! Just read the few lines below and start playing with Cable in 5 minutes!

If you are using Rails, then you already has a app/assets/javascripts/cable.js file that requires action_cable, you just need to connect to the right URL (don't forgot the settings you used), to authenticate using JWT use something like:

(function() {
  this.App || (this.App = {});

  App.cable = ActionCable.createConsumer(
    "ws://localhost:5000/cable?token=JWT_TOKEN" // if using the default options

then on your app/assets/javascripts/channels/chat.js

App.channels || (App.channels = {});

App.channels["chat"] = App.cable.subscriptions.create(
    channel: "ChatChannel",
    room: "1"
    connected: function() {
      return console.log("ChatChannel connected");
    disconnected: function() {
      return console.log("ChatChannel disconnected");
    received: function(data) {
      return console.log("ChatChannel received", data);
    rejected: function() {
      return console.log("ChatChannel rejected");
    away: function() {
      return this.perform("away");
    status: function(status) {
      return this.perform("status", {
        status: status

Then on your Browser console you can see the message:

ChatChannel connected

After you load, then you can broadcast messages with:

App.channels["chat"].send({ message: "Hello World" });

And performs an action with:

App.channels["chat"].perform("status", { status: "My New Status" });


After reading the docs, I realized I'm using some weird naming for variables / methods, so

  • Need to make connection use identifier
  • Add identified_by identifier to Cable::Connection
  • Give better methods to reject a connection
  • Refactor, Connection class is soooo bloated
  • Add an async/local adapter (make tests, development and small deploys simpler)

First Class Citizen

  • Better integrate with Lucky, maybe with generators, or something else?
  • Add support for Kemal
  • Add support for Amber

Idea is create different modules, Cable::Lucky, Cable::Kemal, Cable::Amber, and make it easy to use with any crystal web framework


You know, fork-branch-push-pr 😉 don't be shy, participate as you want!

github statistic
  • 81
  • 0
  • 6
  • 4
  • 6 days ago
  • July 11, 2019

MIT License

Synced at

Thu, 13 May 2021 20:59:43 GMT