Introduction
With social media platforms so prevalent nowadays in our every day lives, it is hard to go a day without utilizing a websocket. Websockets provide a connection between a backend and frontend server to allow for real-time data flow. The simplest, yet one of the most wide use cases of websockets is a simple message board. Here, I’m going to attempt to walk through a simple setup process for a message board with a Rails backend and a React frontend.
Rails Setup
First start off by creating a new rails application:
rails new message-board-backend --api --database=postgresql
Here, we want to use rails as an API as to not clutter up our backend server with frontend items such as views. Additionally, we are using postgresql as our database because sqlite3 is not supported by Heroku in case one day we would like to deploy this app.
Once the app is created, we need to add the ability for the backend to receive requests from the frontend. This is handled through the rack-cors gem. To do this, we will need to uncomment the following line in the Gemfile.
gem 'rack-cors'
We also need enable connections from our frontend. To do this, uncomment and update the following in config/initializers/cors.rb.
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
Setting origins to “*” is alright for development, however, make sure to set this to the production URI before production. Now install the rack-cors gem with:
bundle install
Now we need to create our message model and then migrate. Run the following:
rails g model Message content --no-test-framework
rails db:migrate
Here, we are creating a message model with one column (content) with type string. Also we are using the no-test-framework to tell rails not to generate any test files.
Next, we are going to create our routes inside of config/routes.rb.
Rails.application.routes.draw do
resources :messages, only: [:index, :create]
mount ActionCable.server => '/cable'
end
Here, we generate two routes for messages (index and create). We also generate a route to use as our websocket server endpoint. This is what we’ll use to “listen” for updates on the frontend.
In order for the route we just created to be functional, we need to create a channel for our messages. Channels provide the link for the websocket by broadcasting any new messages that are created. Create a channel in rails with:
rails g channel messages --no-test-framework
This creates a new file in app/channels called messages_channel.rb with methods subscribe and unsubscribe. Edit the file with the following:
class MessagesChannel < ApplicationCabel:Channel
def subscribed
stream_from 'messages_channel'
end
def unsubscribed
end
end
Next, we need to create our controller to handle returning all messages and the creation of new messages. Generate the controller with:
rails g controller messages --no-test-framework
Next we need to configure the new messages controller:
class MessagesController < ApplicationController
def index
messages = Message.all
render json: message
end
def create
message = Message.new(message_params)
if message.save
ActionCable.server.broadcast 'messages_channel', message
head :ok
else
head :ok
end
end
private
def message_params
params.require(:message).permit(:content)
end
end
If a valid message is sent, the message is created in the database, and ActionCable broadcasts the message to the messages_channel channel. If the message is not valid, writing “head :ok” will allow the server to continue the connection.
That does it for the backend. Now we can move on to the frontend.
Frontend
First, start by creating a new react app by entering the following in the terminal.
yarn create react-app message-board-frontend
Next we will need to install react-actioncable-provider to connect to the rails ActionCable. Navigate to the newly created project and run the following in the terminal:
yarn add actioncable
Next we need to set up our index.js file. There really isn’t anything special here. We are really just calling the App component.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<React.Fragment>
<App />
</React.Fragment>,
document.getElementById('root')
);
serviceWorker.unregister();
Now we need to update our App.js file to house our message board
import React from 'react';
import { ActionCable } from 'actioncable';
class App extends React.Component {
constructor() {
super()
this.state = {
messages: []
}
this.cable = ActionCable.createConsumer('ws://localhost:3000/cable')
}
componentDidMount() {
this.fetch
this.createSubscription()
};
fetchMessages = () => {
fetch('http://localhost:3000/messages')
.then(res => res.json())
.then(messages => this.setState({ messages: messages });
}
createSubscription = () => {
this.cable.subscriptions.create(
{ channel: 'MessagesChannel' },
{ received: message => this.handleReceivedMessage(message) }
)
}
mapMessages = () => {
return this.state.messages.map((message, i) =>
<li key={i}>{message.content}</li>)
}
handleReceivedMessage = message => {
this.setState({ messages: [...this.state.messages, message] })
}
handleMessageSubmit = e => {
e.preventDefault();
const messageObj = {
message: {
content: e.target.message.value
}
}
const fetchObj = {
method: 'POST'
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(messageObj)
}
fetch('http://localhost:3000/messages', fetchObj)
e.target.reset()
}
render() {
return (
<div className='App'>
<ActionCable
channel={{ channel: 'MessagesChannel' }}
onReceived={this.handleReceivedMessages}
/>
<h2>Messages</h2>
<ul>{this.mapMessages()}</ul>
<form>
<input name='message' type='text' />
<input type='submit' value='Send message' />
</form>
</div>
);
}
}
export default App;
The above code works as following: First, a consumer is established in the constructor. Then, all messages are fetched from the server and a subscription is created for the MessagesChannel channel. With this subscription, on any received data, the handleReceivedMessage function will execute which will append the new message to the component state. On an updated state, the component will re-render with the new message displayed.
Conclusion
I hope this short blog post helps you set up your own websockets using Rails and ReactJS. Now you have the power to make astonishing applications with real-time updates!
Top comments (0)