Advanced FastAPI Patterns • Lesson 4

WebSocket Rooms & Broadcasting

Learn the connection manager pattern to manage multiple WebSocket clients and broadcast messages to all connected users.

🎯 What You'll Learn

  • Implement a ConnectionManager class for managing WebSocket connections
  • Track active connections and handle connect/disconnect events
  • Broadcast messages to all connected clients simultaneously
  • Use client IDs to identify individual WebSocket connections

WebSocket Rooms & Broadcasting

What You'll Learn

In this lesson you will learn how to manage multiple WebSocket connections and broadcast messages to all connected clients. You will understand:

  • The ConnectionManager pattern for tracking active WebSocket connections
  • How to broadcast messages to all connected clients simultaneously
  • How to identify clients using path parameters
  • How to handle connect and disconnect events cleanly

Theory

The Problem: Multiple Clients

A basic WebSocket echo server handles one connection at a time. But real applications need to manage many clients simultaneously:

  • A chat room where messages from one user appear for everyone
  • A live dashboard that pushes updates to all viewers
  • A multiplayer game that syncs state across players

To manage this, we use the ConnectionManager pattern.

The ConnectionManager Pattern

A ConnectionManager is a class that keeps track of all active WebSocket connections and provides methods to manage them:

from fastapi import WebSocket


class ConnectionManager:
    def __init__(self):
        self.active_connections: list[WebSocket] = []

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

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)

    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)

This centralizes all connection logic in one place, making it easy to manage and extend.

Using the ConnectionManager

Create a single instance and use it in your WebSocket endpoint:

from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()
manager = ConnectionManager()

@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str):
    await manager.connect(websocket)
    await manager.broadcast(f"Client #{client_id} joined")
    try:
        while True:
            data = await websocket.receive_text()
            await manager.broadcast(f"Client #{client_id}: {data}")
    except WebSocketDisconnect:
        manager.disconnect(websocket)
        await manager.broadcast(f"Client #{client_id} left")

Client Identification

Using path parameters like /ws/{client_id} lets you identify who sent each message. The client_id can be:

  • A username
  • A session token
  • A room identifier
  • Any unique string the client provides

Connection Lifecycle with Manager

The flow for each client connection:

  1. Client connects to /ws/{client_id}
  2. Manager accepts the connection and adds it to the list
  3. A "joined" broadcast notifies all other clients
  4. Messages from this client are broadcast to everyone
  5. On disconnect, the connection is removed from the list
  6. A "left" broadcast notifies remaining clients

Sending to Specific Clients

You can extend the manager to send messages to specific clients instead of broadcasting:

class ConnectionManager:
    def __init__(self):
        self.active_connections: dict[str, WebSocket] = {}

    async def connect(self, client_id: str, websocket: WebSocket):
        await websocket.accept()
        self.active_connections[client_id] = websocket

    def disconnect(self, client_id: str):
        del self.active_connections[client_id]

    async def send_personal(self, client_id: str, message: str):
        websocket = self.active_connections.get(client_id)
        if websocket:
            await websocket.send_text(message)

    async def broadcast(self, message: str):
        for websocket in self.active_connections.values():
            await websocket.send_text(message)

This version uses a dictionary keyed by client_id, allowing targeted messaging.

Key Concepts

  • ConnectionManager: A class that tracks and manages all active WebSocket connections
  • active_connections: A list (or dict) storing all currently connected WebSocket objects
  • connect(): Accepts the WebSocket and adds it to the tracked connections
  • disconnect(): Removes a WebSocket from the tracked connections
  • broadcast(): Sends a message to every active connection
  • Client ID: A path parameter used to identify individual WebSocket clients
  • Graceful disconnect: Removing the connection and notifying others when a client leaves

Best Practices

  1. Use a single manager instance shared across all WebSocket endpoints
  2. Always remove connections on disconnect to avoid sending to closed sockets
  3. Handle errors during broadcast -- if one send_text fails, catch the exception so other clients still receive the message
  4. Use dictionaries for client lookup when you need to send messages to specific clients
  5. Keep broadcast messages small -- sending large payloads to many clients can cause performance issues
  6. Consider connection limits -- in production, limit the number of active connections to prevent resource exhaustion
  7. Add authentication -- validate client identity before accepting the WebSocket connection

Additional Resources

💡 Hint

Create a ConnectionManager class with a list of active_connections. Implement connect (append + accept), disconnect (remove), and broadcast (loop and send) methods. Use it in a '/ws/{client_id}' endpoint.

Ready to Practice?

Now that you understand the theory, let's put it into practice with hands-on coding!

Start Interactive Lesson