Scalable websocket with FastAPI and Broadcaster[Redis]

3 min read

Git Commit: websocket based code sharing
Note: The earlier websocket and HTTP endpoints will collide with these new endpoints, make sure you comment/remove them.

In this tutorial we are going to build a codesharing application, It lets us share code in real-time. It could be used for interviewing or for sharing a snippet with colleagues. Our earlier implementation of websocket is not scalable as everything is handled in memory, in a single dictionary, it will only work while the process is running, and will only work with a single process. Let's use the suggested broadcaster library for a more robust implementation.

 

Let's first install the basic requirements. Put these additional requirements in requirements.txt file.

broadcaster==0.2.0
asyncio-redis==0.16.0 

Now, we can utilize broadcaster and its redis based pub-sub capabilities.
 

from broadcaster import Broadcast

from fastapi import FastAPI, WebSocket, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from fastapi.concurrency import run_until_first_complete


broadcast = Broadcast("redis://redis:6379")
templates = Jinja2Templates(directory="templates")
app = FastAPI(on_startup=[broadcast.connect], on_shutdown=[broadcast.disconnect])


@app.get("/{room_id}/", response_class=HTMLResponse)
async def get(request: Request, room_id:str):
    context = {"room_id":room_id,"request":request}
    return templates.TemplateResponse("codeshare.html",context)


async def chatroom_ws_receiver(websocket: WebSocket, room_id: str):
    async for message in websocket.iter_text():
        await broadcast.publish(channel=f"chatroom_{room_id}", message=message)


async def chatroom_ws_sender(websocket: WebSocket, room_id: str):
    async with broadcast.subscribe(channel=f"chatroom_{room_id}") as subscriber:
        async for event in subscriber:
            await websocket.send_text(event.message)


@app.websocket("/{room_id}")
async def websocket_chat(websocket: WebSocket, room_id: str):
    await websocket.accept()
    await run_until_first_complete(
        (chatroom_ws_receiver, {"websocket": websocket, "room_id":room_id}),
        (chatroom_ws_sender, {"websocket": websocket, "room_id":room_id}),
    )

A Broadcast instance is created to facilitate message broadcasting, configured to communicate with a Redis server at the address redis://redis:6379 as defined in our docker-compose service. The broadcast.connect function is set to run during startup, ensuring that the broadcasting mechanism is properly established. Conversely, the broadcast.disconnect function will be executed during shutdown, allowing for a graceful closure of the broadcasting infrastructure. 

Broadcaster is utilizing Redis' pub-sub capabilities internally. The code employs the run_until_first_complete function to concurrently execute the chatroom_ws_receiver and chatroom_ws_sender functions. This concurrent execution ensures that the chatroom can handle both incoming and outgoing messages simultaneously, enabling a seamless real-time chat experience for users.
Let's include the below HTML template for the UI. templates > codeshare.html
 

<!DOCTYPE html>
<html>
    <head>
        <title>CodeShare</title>
        <script src="https://cdn.tailwindcss.com"></script>
    </head>
    <body>
        <div id="room-id" style="display: none;">{{room_id}}</div>
        <form>
            <div class="w-full mb-4 border border-gray-200 rounded-lg bg-gray-50 dark:bg-gray-700 dark:border-gray-600">
                <div class="flex items-center justify-between px-3 py-2 border-b dark:border-gray-600">
                    <div class="flex flex-wrap items-center divide-gray-200 sm:divide-x dark:divide-gray-600">
                        <div class="flex items-center space-x-1 sm:pr-4">
                          <h1 class="text-rose-500 font-bold mx-5">Algoholic</h1>
                        </div>
                    </div>
                </div>
                <div class="px-4 py-2 bg-white rounded-b-lg dark:bg-gray-800">
                    <label for="editor" class="sr-only">Publish post</label>
                    <textarea id="editor" rows="8" class="block w-full px-0 text-sm text-gray-800 bg-white border-0 dark:bg-gray-800 focus:ring-0 dark:text-white dark:placeholder-gray-400" placeholder="Paste/type your code..." required></textarea>
                </div>
            </div>
        </form>
        
        <script>
            let editor = document.getElementById("editor")
            room_id = document.getElementById('room-id').textContent
            let ws = new WebSocket(`ws://localhost:8000/{{room_id}}`);
            console.log("Websocket endpoint is",ws)

            ws.onmessage = function(event) {
                old_value = document.getElementById("editor").value
                document.getElementById("editor").value = event.data
            };

            function sendMessage(event) {
                let input = document.getElementById("editor").value
                console.log("Text are value is: ",input)
                ws.send(input)
            }

            editor = document.getElementById("editor")
            editor.addEventListener("paste", sendMessage);
            editor.addEventListener("input", sendMessage);
        </script>
    </body>
</html>

Let's see this in action :D

FastAPITutorial

My priority is to help build a production-ready application using FastAPI

I prioritize quality over simplicity. Our challenging approach ensures you're prepared for real-world development.

Contacts

Refunds:

Refund Policy
Social

Follow us on our social media channels to stay updated with our latest tutorials and resources.

© Copyright 2022-2025 Team FastAPITutorial. All rights reserved.