Build a Chat system using Rails 5 API Action Cable and ReactJS with multiple private rooms and group chat option

Please note that this post is not a tutorial and it requires knowledge of Rails 5 ActionCable and ReactJS / Javascript custom library building.

One of the awesome features that comes with Rails 5 is ActionCable. With ActionCable, you can build all the real-time features you can think of via websocket. While struggling to build a chat system, I had found multiple examples on the ‘net of how to build a chat app with Rails 5 ActionCable but they are extreme simple to even apply the concept for any real life chat application. I believe this is the first example on the internet that shows you how to build such a chat system with:

  • Rails 5 API backend and a ReactJS frontend
  • Multiple private rooms
  • Any positive number of users in a room (not just 1–1) or group chat

The chat system my talented friend Tim Chang and I have built has:

  1. Multiple private chat rooms
  2. Multiple chat users per room
  3. Online / Offline status of each user
  4. Real-time “typing…” status
  5. Real-time read receipt

In this short post, I’ll show you only the basic of #1 and #2. Please leave me a comment below if you want me to show you how to build #3, #4 and #5. I’m using Rails 5 as the back-end API and ReactJS library on the front-end.

Backend

On creation, Rails will generate the channels folders and files where all the real-time magic happens :)

app/channels/application_cable/channel.rb
app/channels/application_cable/connection.rb

Authentication

First of, let’s authenticate the websocket connection requests to your Rails server inside connection.rb.

Depending on the authentication gem or service that you use in your project, find_verified_user method should be modified to your need. I have a method called valid_token? to verified the access-token and the client_id passed in with the websocket request. If the request is not authenticated, then it will be rejected.

Data Structure

The idea is very basic: a chat room that has multiple messages, each message has a content and a sender. Note that a message doesn’t have a “receiver”. This allows a room to have any number of users since you don’t need to care about the receiver of the messages, since all the messages from the senders will end up appearing in a room regardless of how many participants in the room. So, this is the data structure that I use:

  • Conversation (room): has_many messages, users and has an id
  • Message: belongs_to a conversation, has a sender, has the text content
  • Sender: is a User

As a result, I created 3 models:

Action triggers

When a client connects (subscribed) or broadcasts a message (speak), the backend will react with actions. Inside folder app/channels, I will create a file called room_channel.rb.

As you can see in the comment, after a client “speaks”, the broadcasting is not happening yet; only a new Message is created with its content and data. The chain of action happens after the Message is saved in the DB. Let’s take a look again in the Message model:

after_create_commit { MessageBroadcastJob.perform_later(self) }

Scalability

This callback is called only after the Message is created and committed to the DB. I’m using background jobs to process this action in order to scale. Imagine that you have thousands of clients sending messages at the same time (this is a chat system, why not?), using background job is a requirement here.

Here is when the broadcasting happens. ActionCable will broadcast the payload to the specified room with the provided payload.

ActionCable.server.broadcast(room_name, payload)

Cable Route

You will need to add the /cable websocket route to your routes.rb so that your client can call this endpoint to broadcast and receive messages.

mount ActionCable.server => '/cable'

And that’s it for the backend side! Let’s take a look at the ReactJS front-end library.

Client Library

Please note that depending on the specifics of your project, you will need to understand the concept of this code in this library and modify it to your needs.

First, install the ActionCableJS via npm.

Create a ChatConnection.js file as one of the services in your ReactJs app.

So here is the hook: in createRoomConnection, the client will try to connect with (subscribe to) the RoomChannel we created in the backend, once it’s connected (subscribed), it will stream from the room name ChatRoom-id (look at room_channel.rb above again) Once it’s connected, there are 2 methods that will be called frequently, can you guess which one?

They are: received and speak!

The received method is called when there is a message broadcast to the client from the server, on the opposite, speak is called when the client broadcasts a message to the server.

Voila! That’s it. Again, this is not made to be a ready-to-run-out-of-the-box kind of tutorial because each project is different, but I hope it gives you an idea how to build a chat system with multiple private chat rooms and multiple users per room. Please let me know in the comment section if you have any question.