Try   HackMD

Simple websocket set up (React frontend, Flask backend)

Websockets are a protocol for creating a persistant connection between a client and a server, allowing data to be sent in either direction outside of the typical request/response cycle. This makes them very useful for features that rely on live updates, like chatting, gameplay, and notifications.

The goal of this guide is the successful implementation of websockets in a React app with a Flask backend, including deployment on Heroku. For the purposes of this demo, we will be creating a very simple application, that allows live chatting with all active users. This guide is not intended to cover all of the functionality that websockets can provide—it's just a starting point.

Features like message persistance, chatrooms, direct messaging, and notifications won't be covered in this guide. However, hopefully the steps here will get you pointed in the right direction, and give you an implementation you can start building on.

As you go through these steps, you will probably find it useful to look at the documentation for the socket.io JavaScript library that will be used to set up your websockets in your React app, as well as the documentation for the Flask-SocketIO library that will be used with your Flask server.

Installation and setup

For development

Install flask-socketio in your backend.

pipenv install flask-socketio

Install socket.io-client in your frontend.

npm install socket.io-client

For production

Install eventlet in your backend. (Specifying the version is necessary here since the latest version will not be compatible with your application).

pipenv install eventlet==0.30.2

Update the contents of your requirements.txt file to match your Pipfile.lock (run the following command in your terminal to do this automatically).

pipenv lock -r > requirements.txt

Replace the final line of your Dockerfile (the CMD) with this line:

CMD gunicorn --worker-class eventlet -w 1 app:app

Initialize websockets in your Flask app

First create a file to hold your SocketIO object, inside your app folder.

Inside this file

Import SocketIO from flask_socketio and initialize an instance of a socketio object.

from flask_socketio import SocketIO # create your SocketIO instance socketio = SocketIO()

CORS setup

When you instantiate your app, you must set the appropriate cors_allowed_origins value for the app to work in production. You could use cors_allowed_origins="*" to allow all origins, which would work both locally and in production, although it would be less secure. It would be better practice to set this value to be the actual url of your heroku app.

socketio = SocketIO(cors_allowed_origins=[ "http://actual-app-url.herokuapp.com", "https://actual-app-url.herokuapp.com" ])

However, if we only include our Heroku app on the list of allowed origins, then our websockets won't work when we run the app locally. One solution is to set the allowed origins based on whether the app is running in production or not:

if os.environ.get("FLASK_ENV") == "production": origins = [ "http://actual-app-url.herokuapp.com", "https://actual-app-url.herokuapp.com" ] else: origins = "*" # create your SocketIO instance socketio = SocketIO(cors_allowed_origins=origins)

(There are other approaches you could take here, as well, like setting a value for your allowed origins in your .env file and a separate value in your Heroku config vars, which might be more flexible.)

Handle a chat message

Let's make a very simple event handler. We can use the @socketio.on() decorator to handle specific named events. The decorator will wrap around a function that will manage events of that type.

@socketio.on("event-type") def function_to_handle_event(data_included_with_event): # code to follow

There are some built-in events like "connection" which we could write handlers for. We can also define custom events. In this case, we want to handle chat events, so we will use the label "chat". (On the front-end, we will need to make sure that we use the same value when we emit our events so that they get handled by this function).

@socketio.on("chat") def handle_chat(data): # code to follow

When we recieve a chat message, we are going to broadcast that same message back to everyone who is currently connected. (In most applications, you won't need to broadcast to all connected users—instead you may want to set up chatrooms and send messages to the users who are connected to a specific room—see the documentation here).

We can use the broadcast=True keyword argument when we use the emit function achieve the desired functionality—that will emit the message to all connected users.

# handle chat messages @socketio.on("chat") def handle_chat(data): emit("chat", data, broadcast=True)

Unlike with a Flask route handler, we will not need to have an actual return statement—we send messages explicitly using emit or send functions.

Put it all together

At this point, your file should look something like this:

from flask_socketio import SocketIO, emit import os # configure cors_allowed_origins if os.environ.get('FLASK_ENV') == 'production': origins = [ 'http://actual-app-url.herokuapp.com', 'https://actual-app-url.herokuapp.com' ] else: origins = "*" # initialize your socket instance socketio = SocketIO(cors_allowed_origins=origins) # handle chat messages @socketio.on("chat") def handle_chat(data): emit("chat", data, broadcast=True)

Connect the websocket to your application

In the __init__.py file in your app folder, you will need to connect your socket instance to your application.

# import your socketio object (with the other imports at the # top of the file) # in this example, the file from the previous step is named socket.py from .socket import socketio # initialize the app with the socket instance # you could include this line right after Migrate(app, db) socketio.init_app(app) # at the bottom of the file, use this to run the app if __name__ == '__main__': socketio.run(app)

Front-end React app

In this simple example, the socket will only be relevent on the Chat component, so all of our websocket logic will live on a single component. We can create our socket when the component mounts, and then disconnect when the component unmounts. (If you want to use the same socket connection in components throughout your application, you may want to create a context that makes the socket widely available, or even handle it with redux.)

Initialize your socket

// import the socket import { io } from 'socket.io-client'; // outside of your component, initialize the socket variable let socket;

Open the socket connection in a useEffect

The socket will, by default, connect immediately upon creation.

const Chat = () => { useEffect(() => { // create websocket/connect socket = io(); // when component unmounts, disconnect return (() => { socket.disconnect() }) }, []) // additional code to be added };

Create a listener for chat events

We can create a listener for "chat" events on our websocket on the same useEffect where we created our socket. This listener will be active once the component mounts, and it will remain active until it unmounts, which is the behavior we want.

Let's also use state to store all the messages that we recieve so we can display them.

const Chat = () => { const [messages, setMessages] = useState([]) useEffect(() => { // create websocket socket = io(); // listen for chat events socket.on("chat", (chat) => { // when we recieve a chat, add it into our messages array in state setMessages(messages => [...messages, chat]) }) // when component unmounts, disconnect return (() => { socket.disconnect() }) }, []) };

Create our input form

// use state for controlled form input const [chatInput, setChatInput] = useState("");

In our return statement, we'll need to include a form that we can submit to send our messages.

// include the form in the return statement for the component <form onSubmit={sendChat}> <input value={chatInput} onChange={updateChatInput} /> <button type="submit">Send</button> </form>

Next let's write the sendChat function that will emit our message through the websocket when the form is submitted.

Use socket.emit() to send chat messages through the websocket

When we send our messages, we want to include a username in addition to the message itself. (In this example, we're including the username with the request so our backend doesn't have to interact with the database to get user information—the backend can just directly broadcast this message to all connected users and it will contain all the information we want to include.)

If we are using our redux store to access the information associated with our current user, we'll use useSelector. Depending on the structure of the redux store, the statement we use to get our user information might look something like this:

const user = useSelector(state => state.session.user)

Once we have our user information, we're ready to handle sending chats. We get the message contents from chatInput (the controlled value of our form input), and we get the username from the value of user from our redux store. The socket.emit() method takes a first parameter which is the event name—in this case, "chat". (It needs to match the value in our backend event handler—@socketio.on("chat")—so that it will get handled by the correct function). The next parameter to the emit function will be the data we want to send.

const sendChat = (e) => { e.preventDefault() // emit a message socket.emit("chat", { user: user.username, msg: chatInput }); // clear the input field after the message is sent setChatInput("") }

Display recieved messages

In our return statement, we'll map over the messages array and print our the username and message for each chat.

// include this in the return statement <div> {messages.map((message, ind) => ( <div key={ind}>{`${message.user}: ${message.msg}`}</div> ))} </div>

Put it all together

At this point, your Chat component might look something like this. Make sure to incorporate this component into your application—e.g., if you're using ReactRouter you'll probably want to add a Route for rendering this component to your App.js file.

import React, { useState, useEffect } from "react"; import { useSelector } from "react-redux"; import { io } from 'socket.io-client'; let socket; const Chat = () => { const [chatInput, setChatInput] = useState(""); const [messages, setMessages] = useState([]); const user = useSelector(state => state.session.user) useEffect(() => { // open socket connection // create websocket socket = io(); socket.on("chat", (chat) => { setMessages(messages => [...messages, chat]) }) // when component unmounts, disconnect return (() => { socket.disconnect() }) }, []) const updateChatInput = (e) => { setChatInput(e.target.value) }; const sendChat = (e) => { e.preventDefault() socket.emit("chat", { user: user.username, msg: chatInput }); setChatInput("") } return (user && ( <div> <div> {messages.map((message, ind) => ( <div key={ind}>{`${message.user}: ${message.msg}`}</div> ))} </div> <form onSubmit={sendChat}> <input value={chatInput} onChange={updateChatInput} /> <button type="submit">Send</button> </form> </div> ) ) }; export default Chat;

Summary

On our Flask back-end, we create a SocketIO instance from the flask-socketio package, and used a custom event handler to broadcast all chat messages that are recieved to all connected users.

In our React front-end, we used socket.io-client to create a websocket instance. We added a listener to our socket to handle events where we recieve new chats. We displayed all the chats we recieved, and used a form to submit new chats.