Introduction to WebSockets and the Project
Hey everybody and welcome back to another video. And in this video, we're going to be creating a messaging application using FastAPI and WebSockets. Now, WebSockets are different than HTTP requests and responses that you typically use in modern applications. In HTTP, you send a request to the server, and then the server sends a response back to the client. The connection between the server and the client terminates once the response from the server comes back to the client.
Now, WebSockets are super useful when dealing with applications that need to communicate with one another. Think of how texting, SMS, Messenger, WhatsApp, how they all connect and are able to send real-time data between two or more users. Now that's exactly what we're going to be building in this project. We are going to be building a FastAPI application where two different applications can communicate together real-time using WebSockets. So if this is exciting to you, let's dive into the project and build a Python FastAPI WebSocket real-time data application.
Alright, so let's go over what we already have in this application.
Initial Setup and Basic WebSocket Echo
So I already did a pip install for FastAPI and uvicorn, where we can see that we're using some of the dependencies from FastAPI. We created our FastAPI app. We have an HTML string, which is just going to be our doctype of our HTML page where we have Bootstrap 5. We have some divs and a form to submit the WebSocket request, and then we have some scripts that are also for the WebSockets where we are going to be able to read the messages.
At the bottom, we have an app.get where we are just going to return the HTML response of the variable html, which is the string that's holding all of the HTML code. So now if we go ahead and we just say uvicorn main:app --reload and we open up our web browser, we can see that we get this pretty FastAPI WebSocket chat.
But if we went ahead and typed anything inside, it's not going to work. And we're going to get an error saying that we don't see any libraries for WebSockets and to install this: pip install websockets. So what we can do here is just say pip install websockets. This will install what we need for our WebSockets. We can then come to the top where we say from fastapi import FastAPI, we now want to add the dependency of WebSocket, which is from FastAPI, not from the library we just installed. The library that we just installed will be able to consume the WebSocket as an item within our application.
So what we can do here is now create an endpoint for our WebSocket. So we can say @app.websocket and then make the path parameter of just /ws. async def websocket_endpoint, where we're going to pass in an object of websocket. Where we can then say await websocket.accept(). while True: data = await websocket.receive_text(), and then await websocket.send_text() with our message f"Message text was: {data}".
Alright, so if we came up here and we just do another uvicorn main:app --reload and we open up our browser and we refresh and we say "hello chat" and we click Send, we can see that the message text was "hello chat".
The Problem with Isolated Connections
Now, if we went ahead and created a new endpoint that does the exact same thing, what we won't see though is if we communicate between the two chats, they will not appear on the other web application. And that's because they are not connected to a connection pool for our application. And that's what we'll do here in a little bit. We will add a connection path so it's going to be like normal messaging. So if I say "hello" and I click Send, it's not going to just show up on my screen, it's also going to show up on the other user's screen as well.
So let's go back into the code and we're going to create a connection manager, and our connection manager is going to handle all of our WebSockets and our list of connections.
Building the ConnectionManager Class
So right above our app.get, I'm going to come right here and say class ConnectionManager:. Where we can say def __init__(self): self.active_connections equals a list of WebSockets that are currently an empty list.
We then want to say async def connect, where we pass in our self and our websocket of type WebSocket. Or we can say await websocket.accept() and then self.active_connections.append(our_websocket). So that's if a new user comes to our application, they're going to be added to our active connections.
Now if a user leaves, we want to be able to add a disconnect functionality. So, def disconnect(self, websocket: WebSocket): self.active_connections.remove(the_websocket).
Now, we also want to be able to add a personal message and then be able to broadcast that to all of our connections. So if it's a personal message, we can say async def send_personal_message(self, message: str, websocket: WebSocket): where we can then say await websocket.send_text(of_the_message_itself).
And then if we want to broadcast it we can say async def broadcast(self, message: str): and then for connection in self.active_connections, so for all of the connections we want to then await connection.send_text(of_the_message).
So that's going to broadcast it to everyone that's currently connected to our application through WebSockets. And then we want to create a new manager variable that's equal to our ConnectionManager.
Integrating the ConnectionManager
Alright, and now once we add all of this, let's go down to our @app.websocket("/ws") where we now need to add some more code. So what we can do here is, I'm just going to delete what we currently have. Inside our websocket endpoint, we can add our websocket and then we also want to add a client_id. And now our client_id is going to be our unique identifier for each individual that is currently signed into our WebSocket.
We then want to say await manager.connect(websocket). Then try, and then while True: data = await websocket.receive_text(). await manager.send_personal_message(f"You wrote: {data}"), and then we're going to pass in our websocket. And then await manager.broadcast(), where we can say the client ID says whatever. So inside our broadcast message, it's going to have the unique identifier for that user with all of the messages currently attached.
And then we want to say except WebSocketDisconnect. And we'll need to import WebSocketDisconnect.
manager.disconnect(websocket) and then await manager.broadcast(f"Client #{client_id} has left the chat").
Alright, perfect.
Updating the Frontend and Final Demonstration
And now let's scroll up to our HTML code just a little bit. We need to add a little bit more to this just to make sure that everything is going to work correctly. And just to make this easy to read, under my H1 "FastAPI WebSocket Chat," I'm going to add a new H2 tag that has an ID that shows the user's ID. And then I'm just going to add a new script, and this script is just going to have a little bit more information that also includes our new client ID that we're passing into our chat.
So when we come down to our WebSocket, since we're adding a new client ID here, down in our WebSocket route we need to go ahead and just say /{client_id}. And this client_id is what's going to be passed in right here in our argument.
So now if we go back into our web browser and we refresh the page, we can see now that each user on our web application is going to have a different ID. So this ID is 169 and it ends with 550, while this is 169 and ends with 176. If I say "hello chat" and I click Send, we can see that it's going to print "You wrote: hello chat," and then the message is coming from our client ID, which is this user. And the same thing over here in this application, we can see that the chat came from this user. If I'm over here and say, "Hey, how was your day?" and I click send, we can see that it happens automatically now from the user 550.
So this is awesome stuff. Let's go ahead and create one more user, like this is going to be a group chat setting. And I say "hey" from this user, we can see that this user is now 246. This user was 550, this one's 246. "Hello, I am 246," and I click Send, we can see that the new user is now involved as well. So we have a group chat going on through WebSockets using FastAPI.