Learn how to write a Rust chat application with React on the frontend.
npm create vite@latest wschat-react-rust --template react-ts
This should now scaffold a project within a subfolder of your current working directory called wschat-react-rust
.
For our CSS, we’ll be using TailwindCSS. Tailwind is a utility-first CSS library that allows you to be able to quickly and easily scaffold smaller projects without having to constantly fight media queries by providing utility classes with a mobile-first approach (as a side note: this isn’t necessarily better or worse than regular CSS - this is just how I like to do it!). You can find out how to install it here. It’s quick, easy, and very easy to configure.
Before we start, make sure you delete all of the HTML from the App menu (make sure you return an empty div!), make sure any unnecessary imports are removed and ensure that Tailwind is in your main CSS file.
Here are the contents of my CSS file if you’d like to use my CSS styles exactly:
e.target.value
. This is important as TypeScript needs to know what type our events are, otherwise it’ll complain and refuse to compile.
That’s pretty much it for the main component! We need a modal that can get a name, and then we just need to set up WebSocket functionality. Let’s create our modal:
localhost:8000/ws
. Not particularly useful at the moment because we currently don’t have anything we can connect it to, but we’ll need this for testing later on.
Now that we’ve opened a WebSocket connection, we can add a method for when the connection opens, when it closes, and when we receive a message - like so:
create_message
function at the moment. This function will simply consist of creating a new HTML element, appending some classes and creating the text that will go inside the container div (and appending it to the container), and then appending our message itself to the chatbox as well as scrolling down to the bottom.
cargo-binstall
to install cargo-shuttle:
Cargo.toml
and make sure it looks like the following:
packages.json
level like so:
ws_handler
doesn’t actually exist in our code at the moment. This is what we will be writing next, and it can be simply written as so:
handle_socket
function, as it currently doesn’t actually exist:
shuttle run
to locally run this project and send “Hello” to the WebSocket connection from a front-end web app, on your terminal with the Rust project deployment should return Some("Hello")
, which means we’ve successfully received a WebSocket message. Now we just need to figure out how we’re going to send it back!
Let’s create a function that will broadcast messages along every connected WebSocket:
handle_socket
function, like so:
shuttle_runtime::Secrets
allows us to set environmental variables using a Secrets.toml file, much like how you’d typically use a .env file to be able to use environment variables locally and in production.
Now that our setup for this section is out of the way, let’s cover the admin route first as that’ll mean we can make sure our WebSocket service is complete before we compile any assets. Let’s make a function that will take a user ID and manually disconnect them using the disconnect function we already have:
<base-url>/ws/admin
, as dictated by the nest function. Now if we want to disconnect a user with the user ID of 5 manually for example, we would have to make a post request to <base-url>/ws/admin/disconnect/5
with a Bearer authentication header - as an example, if you’ve set your secret as “keyboard cat”, you need to enter an authentication header of Bearer keyboard cat
.
At this point, your router function should look like this (if not, you have likely missed a step somewhere):
vite.config.ts
file you’ll want to have your defineConfig look like so:
npm run build
, it should build our assets in the API folder in a subdirectory called static
. We can serve this directory to our users on the Rust project, which is great for us as it means we can simply use one deployment instead of having to manage two different deployments. shuttle_static_folder::StaticFolder
has a default value of “static”, so we don’t need to set the folder name manually.
Before we move on, let’s re-write the WebSocket URL so that it will dynamically match whatever the URL of our hosted project will be, instead of a fixed string. Let’s change our WebSocket connection in the React front-end like so:
tower_http
’s ServeDir
function for serving static files:
packages.json
level by setting up an npm script like below:
npm run deploy
, it should build all of our assets into the required folder and then attempt to deploy to Shuttle - assuming there are no issues, it should deploy successfully!
If you would like to change the name of your folder while keeping the deployment name the same, you can do so by simply creating a file called Shuttle.toml
at the Cargo.toml
level and creating a variable for the name
key like in a .env file (so for example if I wanted to call my project keyboard-cat
, I’d type “name=‘keyboard-cat’” into the file).
If you need to check the status of your Shuttle project at any time, you can do so by using shuttle status
at the Cargo.toml
file, or you can add the --name
flag followed by the project’s name to use it from any directory.