Illustrative Usecase: Real-time Chat Application

Updated Nov 18, 2025

To demonstrate the practical application and ergonomics of the proposed Falcon-Pachinko extension, this section outlines its use in building a real-time chat application.

4.1. Scenario Overview

The chat application will support multiple chat rooms. Users can connect to a specific room via a WebSocket URL (e.g., /ws/chat/{room_name}). Once connected, they can send messages, and these messages will be broadcast to all other users in the same room. The application will also support presence indications (users joining/leaving) and typing indicators.

An AsyncAPI document (used as a design artifact) would define the message structures. For example:

  • Client to Server:

  • {"type": "clientSendMessage", "payload": {"text": "Hello everyone!"}}

  • {"type": "clientStartTyping"}

  • {"type": "clientStopTyping"}

  • Server to Client:

  • {"type": "serverNewMessage", "payload": {"user": "Alice", "text": "Hello everyone!"}}

  • {"type": "serverUserJoined", "payload": {"user": "Bob"}}

  • {"type": "serverUserLeft", "payload": {"user": "Alice"}}

  • {"type": "serverUserTyping", "payload": {"user": "Charlie", "isTyping": true}}

4.2. Defining the Chat WebSocket Resource

A ChatRoomResource class would be defined, inheriting from falcon_pachinko.WebSocketResource:

import falcon.asgi
from falcon_pachinko import WebSocketLike, WebSocketResource, handles_message

class ChatRoomResource(WebSocketResource):
    async def on_connect(self, req: falcon.Request, ws: WebSocketLike, room_name: str) -> bool:
        # Assume authentication middleware has set req.context.user
        self.user = req.context.get("user")
        if not self.user:
            return False # Reject connection

        self.room_name = room_name

        await self.join_room(self.room_name)

        await ws.send_media({
            "type": "serverSystemMessage",
            "payload": {"text": f"Welcome {self.user.name} to room '{room_name}'!"}
        })

        await self.broadcast_to_room(
            self.room_name,
            {"type": "serverUserJoined", "payload": {"user": self.user.name}},
            exclude_self=True
        )
        return True

    @handles_message("clientSendMessage")
    async def handle_send_message(self, ws: WebSocketLike, payload: dict):
        message_text = payload.get("text", "")
        # Add validation/sanitization as needed
        await self.broadcast_to_room(
            self.room_name,
            {"type": "serverNewMessage", "payload": {"user": self.user.name, "text": message_text}}
        )

    @handles_message("clientStartTyping")
    async def handle_start_typing(self, ws: WebSocketLike, payload: dict):
        await self.broadcast_to_room(
            self.room_name,
            {"type": "serverUserTyping", "payload": {"user": self.user.name, "isTyping": True}},
            exclude_self=True
        )

    @handles_message("clientStopTyping")
    async def handle_stop_typing(self, ws: WebSocketLike, payload: dict):
        await self.broadcast_to_room(
            self.room_name,
            {"type": "serverUserTyping", "payload": {"user": self.user.name, "isTyping": False}},
            exclude_self=True
        )

    async def on_disconnect(self, ws: WebSocketLike, close_code: int):
        if hasattr(self, 'room_name') and hasattr(self, 'user'):
            await self.broadcast_to_room(
                self.room_name,
                {"type": "serverUserLeft", "payload": {"user": self.user.name}},
                exclude_self=True 
            )
            await self.leave_room(self.room_name)
        print(
            f"User {getattr(self.user, 'name', 'Unknown')} disconnected from room"
            f" {getattr(self, 'room_name', 'N/A')} with code {close_code}"
        )

    async def on_unhandled(self, ws: WebSocketLike, message: Union[str, bytes]):
        # Fallback for messages not matching handled types or non-JSON
        print(f"Received unhandled message from {self.user.name} in {self.room_name}: {message}")
        await ws.send_media({
            "type": "serverError",
            "payload": {"error": "Unrecognized message format or type."}
        })

This example demonstrates how the WebSocketResource streamlines the development of a feature-rich chat application. The abstractions for room management and typed message handling significantly reduce boilerplate code.

4.3. Routing and Application Setup

In the main application file:

import falcon.asgi
import falcon_pachinko
import asyncio
from falcon_pachinko.workers import WorkerController

# Assuming ChatRoomResource is defined as above
# Assuming an authentication middleware `AuthMiddleware` that sets `req.context.user`

app = falcon.asgi.App(middleware=[AuthMiddleware()])
falcon_pachinko.install(app)
conn_mgr = app.ws_connection_manager

# Add the WebSocket route
app.add_websocket_route('/ws/chat/{room_name}', ChatRoomResource)

# Define a background worker
async def system_announcement_worker(conn_mgr: falcon_pachinko.WebSocketConnectionManager) -> None:
    while True:
        await asyncio.sleep(3600) # Every hour
        announcement_text = "System maintenance is scheduled for 2 AM UTC."
        chat_room_ids = await conn_mgr.get_rooms_by_prefix("chat_")
        for room_id in chat_room_ids:
            await conn_mgr.broadcast_to_room(
                room_id,
                {"type": "serverSystemAnnouncement", "payload": {"text": announcement_text}}
            )

# Manage the worker with the ASGI lifespan
controller = WorkerController()
@app.lifespan
async def lifespan(app_instance):
    await controller.start(system_announcement_worker, conn_mgr=conn_mgr)
    yield
    await controller.stop()

4.4. Client-Side Interaction (Conceptual)

A JavaScript client would interact as follows:

  1. Connect: const socket = new WebSocket("wss://example.com/ws/chat/general");

  2. Send Messages:

   socket.send(JSON.stringify({type: "clientSendMessage", payload: {text: "Hi there!"}}));
   socket.send(JSON.stringify({type: "clientStartTyping"}));
  1. Receive Messages:
   socket.onmessage = function(event) {
       const message = JSON.parse(event.data);
       switch(message.type) {
           case "serverNewMessage":
               // Display message.payload.user and message.payload.text
               break;
           case "serverUserJoined":
               // Update user list with message.payload.user
               break;
           // Handle other message types...
       }
   };

This illustrative use case shows how Falcon-Pachinko can provide a comprehensive and developer-friendly solution for building real-time applications on Falcon.