Simple chat application using Websockets with FastAPI
Tags: fastapi, python, websocket
Published: 2021-01-31 00:52:30

**Originally published on my** [blog](https://www.gealber.com/simple-chat-app-websockets-fastapi)

Hey Guys, here I'm again tinkering with FastAPI. This time I'll be trying to build an **extremely** simple *chat* web application. I'll be using **Websockets** with FastAPI. 

A full explanation of [Websockets](https://en.wikipedia.org/wiki/WebSocket) will need a whole article, so I'm assuming that the reader have some familiarity with this protocol. That is more like an upgrade of [HTTP](https://developer.mozilla.org/es/docs/Web/HTTP) instead of a protocol itself.

Another valid point to be made, is that this was a result of me hacking around with the [FastAPI](https://fastapi.tiangolo.com) documentation. So as a result, I haven't taught very deeply on a better approach. On another post I will present you a more mature approach to this problem. For now let's just have fun learning stuffs.

## What we want to build? Partial description of requirements.

Before we start coding let's think first what we want to make. Well the idea is to build a simple web page, that simulate the behavior of a chat room.

Desired behavior:
- Open the page on a browser.
- All the clients connected to the same URL, but through different browsers, can chat between them.

## Basic design of the behavior

Client1 and Client2 connect to the URL, let's say is <http://localhost:8000>. After this connection, every message from Client1 will be broadcasted to the others clients connected on the *room*, in this case would be Client2. So a broad view of the pattern would be like in the ASCII picture:

```

   Client 1

     ___T_                              SERVER (FastAPI)
    | O O |                             +---------+
    |__n__|           Message 1         |         |
 >===]__o[===<    --------------->      |         |
     [o__]            Message 2         |         |
     ]| |[        <--------------       |         |
    [_| |_]                             |         |
                                        |         |
                                        |         | 
   Client 2                             |         |
                                        |         |
     ___T_                              |         |
    | O O |                             |         |
    |__n__|          Message 2          |         |
 >===]__o[===<   --------------->       |         |
     [o__]           Message 1          |         |
     ]| |[       <--------------        |         |
    [_| |_]                             +---------+

```
> Robot ascii generated with [go-asciibot](https://github.com/mattes/go-asciibot)

Client 1 send message to the server, server broadcast the message to the other clients on the *room*.

## Requirements

* FastAPI

```console
$ pip install fastapi
```

* Uvicorn

```console
$ pip install uvicorn
```

## Server code

Let's jump into the code. I will divide it on two sections, one for the server code and the other for the client code. 

Well as I said previously I'll need an endpoint to serve the *HTML* page, that in this case will contain the client code. So we'll need to learn how to serve an *HTML* with __FastAPI__. Our HTML code will be on the file `index.html`, on the same folder as the `main.py`.

```python

from fastapi import FastAPI

app = FastAPI()

html = ""
with open('index.html', 'r') as f:
    html = f.read()

@app.get("/")
async def get():
    return HTMLResponse(html)
```

So what's happening here? First we make a simple import of the *FastAPI* class, to be able to create our application. Next to that, we read the content of the `index.html` file and store its content on the variable called *html*. There's must be a better way to do this, but for our case it solves our problems.

You may be wondering what is the content of the *HTML* file, we'll see don't worry.

And for last we define our endpoint, where we can access the content of the *index.html* file. In this way if you visit <http://localhost:8000> the browser will render the *HTML*.

So what's next? Until now, we're only able to render the content of the *HTML*, but we haven't handled yet other situations, right? How do we differentiate a client from another one? Let's solve this.

```python
from typing import List
from fastapi import FastAPI, WebSocket
from fastapi.responses import HTMLResponse

class ConnectionManager:
    def __init__(self):
        self.connections: List[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.connections.append(websocket)

    async def broadcast(self, data: str):
        for connection in self.connections:
            await connection.send_text(data)


manager = ConnectionManager()


@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
    await manager.connect(websocket)
    while True:
        data = await websocket.receive_text()
        await manager.broadcast(f"Client {client_id}: {data}")
```

The class called `ConnectionManager`, as the name suggests, is the class we're going to use to handle the connection of different clients. 

- The `connect` method on this class, take as argument a Websocket client. This client start to accept message from the browser and is added to a list with all the clients.
- The `broadcast` method, take as argument a `message` and broadcast its content to any other client on the *room*.

Every client will connect to <http://localhost:8000/ws/{client_id}>, where `{client_id}` will be a integer that identify this client. After it is connected it will start accepting messages from the browser.
Any of this message will be broadcasted later to any client on the *room*.


## Client code

Now comes to see the client code, that in our case will be the *HTML* code.

```html

<!DOCTYPE html>
<html>
    <head>
        <title>Chat</title>
    </head>
    <body>
        <h1>WebSocket Chat</h1>
        <button onClick="showForm(event)" id="connect">Connect</button>
        <form action="" onsubmit="sendMessage(event)" id="form" style="display: none">
            <input type="text" id="messageText" autocomplete="off"/>
            <button>Send</button>
        </form>
        <ul id='messages'>
        </ul>
        <script>
            var clientID = Date.now();
            var ws = new WebSocket(`ws://localhost:8000/ws/${clientID}`);

            function processMessage(event) {
                var messages = document.getElementById('messages')
                var message = document.createElement('li')
                var content = document.createTextNode(event.data)
                message.appendChild(content);
                messages.appendChild(message);
            }

            ws.onmessage = processMessage;

            function sendMessage(event) {
                var input = document.getElementById("messageText")
                var message = document.createElement('li')
                var content = document.createTextNode(input.value)
                message.appendChild(content);
                messages.appendChild(message);
                ws.send(input.value);

                input.value = ''
                event.preventDefault()
            }
            
            function showForm(event) {
                var button = document.getElementById("connect");
                var form = document.getElementById("form");
                button.style.display = "none";
                form.style.display = "block";
            }

        </script>
    </body>
</html>

```

Ups!! That's a lot of information, so let's chunk it and try to understand at least the most important parts. The pure *HTML*, has two principal components, the *form* and the *button*. 

- The *button*. After it's clicked the button will be hidden from the client, and the *form* will be visible. Only that no more tricks. This is done through `javascript`, on the `showForm` method.

- The *form*, contains an input field where the messages are written, and a `send` button that will trigger the method `sendMessage` on the JavaScript side.

After we press the `send` button, we will send the text to the server using the Websocket connection. In this way when the server gets the message it will broadcast it to any other client on the *room*.

# How to run it?

```console
uvicorn main:app --reload
```

The `--reload` flag is to be able to recover on failure, very useful.

That's all folks, and definitely need to improve my writing skills in the meanwhile I'll keep practicing.

# Testing the application

Open at least two web browsers and connect to <localhost:8000>