An ActionCable "port" to Crystal, framework agnostic, 100% compatible with the ActionCable JS Client lucky-framework actioncable websockets realtime
HEAD Latest release released
72 6 2


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

  • [x] Need to make connection use identifier
  • [x] Add identified_by identifier to Cable::Connection
  • [x] Give better methods to reject a connection
  • [x] 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: cable-cr/cable
License MIT
Crystal 0.35.0


Dependencies 3

  • habitat
    {'github' => 'luckyframework/habitat'}
  • redis ~> 2.6.0
    {'github' => 'stefanwille/crystal-redis', 'version' => '~> 2.6.0'}
  • schedule
    {'github' => 'hugoabonizio/'}

Development Dependencies 0

Dependents 0

Last synced .
search fire star recently