Use React and Rust to Build a Real-Time Video Chat App
Time to read: 12 minutes
Use React and Rust to Build a Real-Time Video Chat App
Have you ever wondered what it would be like to build a video chat application with Rust? It’s easier than you might imagine, and with this tutorial I will show you how to use the Zoom Video SDK to build a video chat app. It will use React for the frontend and Rust (Axum) for the backend.
Requirements
To follow this tutorial, you will need the following:
- A Zoom Video SDK account
- A basic understanding of Rust
- Rust ≥ 1.67 and Cargo
- Yarn
Getting started
Quick overview
To start (or join) a session, you first generate a JSON Web Token (JWT) on the backend. After that, you initiate a client on the frontend using the Video SDK and render the video stream onto a designated HTML element.
In addition to that, the Zoom Video SDK allows you to determine video size and position, as well as giving you control over the video and audio streams. I will show how to take advantage of these features later on.
The process flow of the application on the frontend is as follows:
- The frontend displays the list of currently available rooms. The user can either select from the list or create a new one. To create a new room, a form will be displayed requesting the room name and passcode (if required).
- The room selected by the user is then passed to the backend. If the room has a passcode, the associated passcode is passed along.
- For a provided room, the backend will then generate and return an access token. If a passcode is required to join the room, this passcode will be validated before the token is generated.
- This token is then used by the frontend application to connect to the room in question.
Create the application
To get started, create a new folder named video_app, where you create your Rust applications, which will have two sub-folders named frontend and backend, by running the commands below.
Build the application's backend
Change into the backend folder to begin creating the backend of the application. Now, start by initialising Cargo in the directory using the following command.
Next, add the project's dependencies. To do that, update the dependencies
section of the Cargo.toml file to match the following.
Here's a quick breakdown of the dependencies:
- axum: This library will be used to handle incoming requests and pass the appropriate response.
- dotenvy: dotenvy helps with loading environment variables; a huge time-saver during development. It is a well-maintained version of the dotenv crate.
- jsonwebtoken: This library will be used to create the JWTs.
- MD5: This library will be used to generate hashes for the application.
- Rand: This library will be used to generate random identities for the video chat rooms.
- Serde, Serde Derive, and Serde JSON: These packages reduce the complexity of deserialising JSON responses from Twilio, so that they can be more easily used.
- SQLx: This library will be used to interact with the database.
- Tokio: This library provides an async runtime.
- Tower-HTTP: This library will be used to implement a CORS layer for the API. Since the frontend and backend will be running on separate ports, CORS will need to be enabled.
Next, in the backend directory, create a new file named .env to store the environment variables for the application. In it, paste the following code.
You can modify the FRONTEND_URL
or SERVER_PORT
variables, should the specified ports be in use on your workstation.
Get your Zoom Video SDK credentials
To generate your Zoom Video JWT, sign into your Video SDK account, go to the Zoom App Marketplace, hover over Develop, and click Build Video SDK.
Then, scroll down to the SDK credentials section to see your SDK key and secret.
Replace the respective placeholder values in .env with the SDK Key and SDK Secret keys copied from the console.
Create helper functions
Next, add some helper functions to avoid duplicating code across the project. In the src folder, create a new file named helper.rs and add the following to it.
This module has two functions namely:
- The
hash()
function, which is used to encrypt a secret. For this tutorial, the MD5 algorithm is used for hashing. While this is insecure for production applications, it suffices for demonstration purposes as used in this tutorial. - The
identity()
function, which is used to generate a unique identity. This allows your application to handle rooms with the same name without any unwanted side effects.
Create data models
Next, create the models for the application. In the src folder, create a new file named model.rs and add the following code to it.
The Claim
struct is used to model the payload section of a token. This will be encoded and signed to generate an access token for the frontend.
The NewRoomRequest
, JoinRoomRequest
, and RoomResponse
structs are used by Axum to parse incoming requests, and, where required, a response pertaining to a Room
.
Finally, the Room
struct specifies the fields required to manage a room. In addition, it adds an implementation function for validating room passcodes. It also provides a custom implementation for the Serialize trait. Using the custom serialisation, you avoid returning the passcode in the JSON response. The FromRow
attribute indicates that this struct corresponds to a table in the database.
Set up the database
For the database, you’ll be using SQLite in conjunction with SQLx. Create a file named data.db at the root of the backend folder using a text editor or the touch command. This file will hold the application database.
To simplify managing the database, SQLx provides a CLI which you can install using the following command.
Next, create a migration file to handle creating the room
table using the following command.
The newly created migration file will be located in the migrations folder and its name will be prepended with the timestamp when the file was created and end with create_room_table.sql. Open the newly created file and add the following SQL command to it.
Next, create the empty SQLite database file and then run your migration using the following commands.
In the src folder, create a new file named database.rs and add the following code to it.
This module contains two structs:
The
AppState
struct is an extractor for the Axum application state in the Axum application. This state will save the database connection pool for the application.The
Database
struct holds the connection pool. It also has implementation functions which simplify the processes of creating a room, getting all rooms, and getting a single room with the specified identity.
Next, run the following command.
When you use the query!
or a query_as!
macro, you'll need to use the above command to generate JSON files for your queries. This gives you compile-time safety, as SQLx will check your code against the generated files during compile time. If anything is wrong, it'll automatically detect and display an appropriate error message.
Add a module for token generation
In the src folder, create a new file named token.rs and add the following code to it.
The token()
function takes the room identity and a unique identifier for a user. Using these parameters, the jsonwebtoken
crate and the retrieved .env variables, a JWT is created and returned.
Create handler functions
The next thing to add is a module containing handler functions for API requests. Create a new file named handler.rs in the src folder, and add the following code to it.
The backend will have four endpoints to handle the following:
Create a new room. This is handled by the
create_room()
functionGet all active rooms. This is handled by the
get_all_rooms()
functionGet a room by the specified id. This is handled by the
get_room()
functionGet a token for a specified room. This is handled by the
get_room_token()
function
Finally, update src/main.rs to match the following code.
The main()
function has been updated to perform the following actions:
Creates a new Axum router and bind the endpoints to the corresponding handler function
Adds a CORS layer to the router to restrict the origin, methods, and headers that will be honoured by the API
Binds the backend to the port specified in the .env file
Start the back end of the application
Next, start the backend of your application with the following command.
Build the frontend
In a new terminal tab or window, change into the frontend folder and start a new project using the following commands.
Next, add the project dependencies using the following command:
The dependencies are as follows:
Ant Design: This is a component library which makes it simpler to build user interfaces
Ant Design Icons: This gives you access to AntD’s SVG icon collection
Axios: Axios will be used to make API requests and get the appropriate response
React Router: This will be used to implement client-side routing
Zoom Video: This library simplifies interacting with the Zoom Video SDK, which provides video, audio, screen sharing, chat, data streams, and more, as a service
Add functionality for making API calls
In the frontend/src folder, create a new file named Api.js and add the following code to it.
The getRooms()
, createRoom()
, getRoomToken()
, and getRoom()
functions are used to make API calls to the backend, handling any errors that may be encountered, otherwise returning the requested information.
Add hook for rendering notifications
Notifications are used to show the result of a user’s actions. Different components will require this functionality, in one way or another, making it more efficient to have the functionality implemented as a hook.
In the frontend/src folder, create a new folder named hooks, and in it a new file named useNotification.js. Add the following code to the newly created file.
The useNotification
hook returns an array with three components:
The
contextHolder
which is where the notification will be renderedThe
showFailure()
function which renders an error notificationThe
showSuccess()
function which renders a notification for a successful action
Add a hook for Zoom functionality
Zoom will be used to handle the video and audio functionality. Create a new file in the frontend/src/hooks folder named useZoom.js, and add the following code to it.
This hook handles the connection to a session, rendering of video streams, and leaving of a session. To initialise it requires four parameters, namely:
The id of the
<video>
element on which the current user video will be renderedThe id of the
<canvas>
element on which the video stream of other participants will be renderedA boolean named
showVideo
which determines whether or not the current user video should be displayedA boolean named
isMuted
which indicates whether or not the current user’s mic is muted
Next, two useEffect
hooks are declared to start or stop the current user’s video or audio stream depending on the values of showVideo()
and isMuted()
.
The renderParticipant()
function is used to render a single participant video on the canvas. It does this by calling the renderVideo()
function on a Zoom Video stream. This function takes seven parameters, namely:
A
<canvas>
element on which the participant video can be rendered. You can use the same element to render multiple participants as will be done in this tutorial.The identifier of the participant whose video is to be rendered
The width and height of the video being rendered
The x and y axes coordinate of the video on the canvas
The resolution of the video
For the application, your video will be rendered separately from that of other participants. Hence, when rendering the participants, you will need to exclude the current user from the list. This is done using the filteredParticipants()
function. This function also returns a maximum of six participants as that will be the maximum number of participants to be rendered in this tutorial.
The join()
function is called when a user wants to join a session. This function requires three parameters: the session name, a valid JWT, and the user identifier (whom the JWT is associated with).
Using these parameters, a Zoom Video client is instantiated and used to start a video stream; one for the logged in user (rendered on a <video>
element with id self-view-video
, and one for the rest for the other participants in the video session, rendered by a call to the previously mentioned renderParticipants()
function.
The leave()
function is called when a user wants to leave a session. This function stops the current video and audio stream (if initialised), and calls the leave()
function on the Zoom Video client.
The join()
and leave()
functions are returned in the array.
Add UI components
The next step is to add the components for your frontend. Your application will have the following components:
AddRoom: This component contains the UI and logic for adding a new room
JoinRoom: This component contains the UI and logic for joining a room
ModalForm: This component is used to render form components in a modal
Rooms: This component contains the UI and logic for listing all the active rooms
In the frontend/src folder, create a new folder named components, and in it, a new file named AddRoom.jsx and add the following code to it.
This component has four props:
onAddition():
This is a callback function triggered once a room has been added successfullyonError()
: This is a callback function triggered when an error is encountered while adding a roomisVisible
: This is a boolean which determines whether or not the form is visiblesetVisibility
: This function toggles the form's visibility
A function named sanitizePasscode()
is used to properly format the room passcode. If an empty string is provided as the room's passcode, it is converted to NULL
.
The handleFormSubmission()
function takes a form element with which it validates the submitted data. If everything checks out, the API call is made to create a new room, and then the onAddition()
callback is executed with the newly created room passed as a parameter. The form is thereafter cleared and hidden from the user.
This component renders a ModalForm
with two children, the input fields for the room name (required) and passcode (optional).
Next, in the frontend/src/components folder, create a new file named JoinRoom.jsx and add the following code to it.
To determine which room is being joined, the useParams hook is used to get the room identity. On page load, the room details are retrieved via an API call to the backend, and saved to state. If the room does not have a passcode, a token is generated via a second API call, and with that the join()
function (from useZoom.js) is called.
If the room requires a passcode, a form is first displayed prompting the user to provide a passcode, which is then authenticated before a token is returned by the backend. Also, if at any point the user cancels the operation, the application redirects the user to the home page.
Next, in the frontend/src/components folder, create a new file named Rooms.jsx and add the following code to it.
On load, this component retrieves all the active rooms from the backend and renders them in a list. It also renders the AddRoom
component which allows the user to add a new room immediately.
The last component is the ModalForm
component. In the frontend/src/components folder, create a new file named ModalForm.jsx and add the following code to it.
This component allows you to wrap form items in a modal while customising the title, visibility, and behaviour of the form to suit the different needs of each component.
Finally, in the frontend/src folder, update the App.jsx file to match the following.
In this component, you created a new router object which determines the component to be rendered for a given path. This object is provided as a prop to the RouterProvider component provided by React Router.
Start the front end of the application
With everything in place, start your frontend using the following command.
The default index page shows a list of rooms that can be joined. Since you have none, it should look like this:
Click on the Add Room button to create a new room. Fill the form that pops up as shown below and click OK to complete the process.
Now that you have a room, click on it from the list to start a video session. A recording of the application hosting two participants is shown below.
That's how to use React and Rust to build a video chat app
There you have it. You have successfully integrated the Zoom Video SDK with your React frontend and Rust backend. There’s still a lot more that you can do with the SDK — for example screen sharing and chat.
You can review the final codebase for this article on GitHub should you get stuck at any point. I’m excited to see what else you come up with. Until next time, make peace not war ✌🏾
Joseph Udonsak is a software engineer with a passion for solving challenges — be it building applications, or conquering new frontiers on Candy Crush. When he’s not staring at his screens, he enjoys a cold beer and laughs with his family and friends. Find him at LinkedIn, Medium, and Dev.to.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.