Build Your Chat Application on React & Rails

Gunjan Solanki
6 min readFeb 28, 2021

Millions of users around the world spend hours on social applications like What's App, Google Meet, Microsoft teams every day. Have you ever thought to build an application with scratch with the same functionalities you come across in your day-to-day life?

Recently, I started learning React and thought to build a real-time application with React and Ruby on Rails. This was also my first time trying a hands-on Real-time application using Action Cable and it worked finally.

I know, this might not be the sexiest app but it will definitely provide you a foundation to build app on React with Rails.

Here, we will talk about building a messaging application with the following features :

  • JWT Authentication: Register and login functionality for users built on JWT token authentication. You will see later on, we authenticate all the API requests from the front-end using JWT tokens.
  • Chat Rooms: Registered users can create a Room to chat and add other users too in that room. So, it can be a public room/private room to chat based on the number of users added.
  • Action Cable: To integrate the web-sockets part, Rail comes with the action cable framework with Client-side JavaScript & Server-side Ruby. Action Cable Terminologies —
  1. Connection: To stream data between the client and server, a connection needs to be established to which the user subscribes. A web-sockets server can open many connections at the same time enabling users to operate on multiple devices.
  2. Consumer: Consumer is a client of a web socket connection.
  3. Channel: A channel to which a consumer subscribes and broadcasts/receives the data. Here, we have created two channels one is a Chatrooms Channel & a Messages Channel.
  4. Subscriber: A consumer is itself a subscriber here to a channel through it will connect or say subscribe in action cable words. For example, here a consumer/user could subscribe to multiple chat rooms at the same time.

Here is a reference for action cable detailed explanation :

Database Schema & Associations: Schema forms the structure of any application and should be the first step while building it.

DB ASSOCIATIONS

We have used gem “bcrypt” to securely store password in database. You can see the field password_digest in users table and a helper method has_secure_password in User model.

SCHEMA

Now we will move on Step-by-step by laying the foundation of the Application with Rails and later integrate the React library as the View Component of the MVC Architecture :

  • Rails Setup — Routes, Models, Controllers, Action Cable Channels :
  1. Initialize your Rails app (you can skip the view/tests layer). Add config.api_only = true in application.rb.
  2. Fix the CORS issues which might come while making API requests from frontend to rails API by adding gem ‘rack-cors’ and setting origin to ‘*’ in cors.rb.
  3. Setting token authentication with JWT :
  • Add below gem -
# Use jwt for token authenticationgem 'jwt'
  • Add actions to encode and decode tokens —
class ApplicationController < ActionController::API  def encode(payload)    JsonWebToken.encode(payload)  end  def decode(token)    JsonWebToken.decode(token)  endend
  • User Registration — token creation
def create  user = User.new(user_params)  if user.save
payload = {'user_id': user.id}
token = encode(payload) render json: { user: UserSerializer.new(user), token: token, authenticated: true, } else render json: { message: 'There was an error creating your account' } endend
  • React: Token authentication when the user logs in-
fetch(`${APP_URL}/api/v1/login`, {  method: 'POST',  headers: {     'Content-Type': 'application/json',     'Accept': 'application/json'  },  body: JSON.stringify({user: {          username: this.state.username,          password: this.state.password  }})}).then(response => response.json())  .then(result => {    if (result.authenticated) {      localStorage.setItem('jwt_token', result.token)      this.props.updateCurrentUser(result.user.data)   } else {     alert('Password/Username combination not found')   }})
  • Every time an API call is made by any user we use the token generated to authenticate the request.

4. Generate models and add associations, validations accordingly. Create channels — ChatroomsChannel & MessagesChannel. Add routes —

namespace :api do  namespace :v1 do    post '/login', to: 'auth#create'    resources :messages, only: [:index, :create]    resources :users, only: [:index, :create] do      member do        get 'chatrooms'      end    end    resources :chatrooms, only: [:index, :create, :show]  endend
mount ActionCable.server => './cable'
  • You can see action cable server is mounted to receive all requests on /cable.

5. Initialize React app inside the root folder where we keep all the front end code to maintain — Separation of concerns which is also a design principle of MVC.

npx create-react-app frontend

There are other ways too to use React with rails by using gem “react_on_rails”, or by React-rails gem and webpacker gem but here we tried to keep our backend separate with Frontend application.

/chat-app/frontend/src/index.jsconst CableApp = {};CableApp.cable = actionCable.createConsumer(APP_CABLE_URL);ReactDOM.render(  <React.StrictMode>    <Router>      <App cableApp={CableApp} />    </Router>  </React.StrictMode>,  document.getElementById('root')
);
/chat-app/frontend/src/constants/index.jsexport const APP_URL = "http://localhost:3000"export const APP_CABLE_URL="ws://localhost:3000/cable";

5. Setup APIs & serializers —

  • We have used gem ‘fast_jsonapi’ which is better than commonly used Active Model Serializers in terms of performance. Read here.

You can now add serializers referring to below Chatrooms serializer —

/chat-app/app/serializers/chatroom_serializer.rbclass ChatroomSerializer  include FastJsonapi::ObjectSerializer  attributes :id, :title, :users, :messages  attribute :users do |room|    UserSerializer.new(room.users.uniq)  endend

Here is the demonstration of the messaging feature how React interacts with Rails APIs to allow users to chat —

  • Websocket Connection & receiving broadcast messages at frontend —

We created a WebSocket component that contains a received function that listens to the data broadcasted from the Rails Chatrooms channel and updates the room data.

chat-app/frontend/src/components/ChatRoom/ChatRoomWebSocket.jsclass ChatroomWebSocket extends Component {   componentDidMount() {     // used getRoomData() to render data on ChatroomShow component    this.props.getRoomData(window.location.href.match(/\d+$/)[0])    // the to send params to the subscribed action in   ChatroomsChannel   this.props.cableApp.room =    this.props.cableApp.cable.subscriptions.create({     channel: 'ChatroomsChannel',     room: window.location.href.match(/\d+$/)[0]   },   {      received: (updatedRoom) => {      this.props.updateApp(updatedRoom)    }  })}

We call this component inside the ChatRoomShow component where the message gets created and broadcasted by the channel at the backend.

Note that we need to pass the cable app in our prop which we passed from our App component where package “actionCable” is used to

<ChatroomWebSocket  cableApp={this.props.cableApp}  getRoomData={this.props.getRoomData}  roomData={this.props.roomData}  updateApp={this.props.updateApp}/>
  • React states used — These states get changed throughout the application and passed via props or used in APIs as required. It's better to use a state management system like Redux. It's just for the demo purpose.
this.state = {  currentUser: null,  currentUserRooms: [],  currentRoom: {    chatroom: [],    users: [],    messages: []  }}

Here is the source code for your reference —

https://gitlab.com/gsolanki1/chat-app

I might have been much focused on the backend part and but you can obviously build a better UI than this using material UI or go through some blogs on react chat UI which helped me too.

I will soon come up with the second part of the blog where we will build features like video calling and screen sharing without using any third-party API. Please clap if you really learned something from this blog.

--

--