WebSocketsWebSockets

WebSockets

After mastering Server-Sent Events for one-way communication, Batman realized he needed something more powerful. When Commissioner Gordon wanted to chat with him in real-time during crisis situations, Batman needed bidirectional communication.

"SSE is great for pushing updates to my dashboard," Batman thought, "but I need two-way communication for coordinating with my allies!"

To handle real-time bidirectional communication, Batman learned how to work with WebSockets using Robyn's modern decorator-based API. Under the hood, messages flow through Rust channels for maximum performance — no Python GIL overhead during message dispatch.

Request

WebSocket
/web_socket
from robyn import Robyn

app = Robyn(__file__)

@app.websocket("/web_socket")
async def handler(websocket):
    while True:
        msg = await websocket.receive_text()
        await websocket.send_text(f"Echo: {msg}")

app.start()

Receiving Messages

The receive_text() method blocks until the next message arrives from the client. It is backed by a Rust tokio::mpsc channel, so the Python handler genuinely suspends without holding the GIL.

When the client disconnects, receive_text() raises WebSocketDisconnect. You can either catch it explicitly or let the internal wrapper handle it silently.

Receiving Messages

WebSocket
/web_socket
@app.websocket("/ws")
async def handler(websocket):
    try:
        while True:
            msg = await websocket.receive_text()
            await websocket.send_text(f"Got: {msg}")
    except WebSocketDisconnect:
        print(f"Client {websocket.id} disconnected")

Sending Messages

To send a message to the current client, use send_text() or send_json(). All send methods are async.

Sending Messages

WebSocket
/web_socket
@app.websocket("/ws")
async def handler(websocket):
    while True:
        msg = await websocket.receive_text()
        await websocket.send_text(f"Echo: {msg}")

Broadcasting

To send a message to all connected clients on the same WebSocket endpoint, use the broadcast() method.

Broadcasting

WebSocket
/chat
@app.websocket("/chat")
async def handler(websocket):
    while True:
        msg = await websocket.receive_text()
        # Send to all connected clients
        await websocket.broadcast(f"User {websocket.id}: {msg}")
        # Also send a confirmation to this client only
        await websocket.send_text("Your message was sent")

Query Parameters

You can access query parameters from the WebSocket connection URL via websocket.query_params.

Query Params

WebSocket
/ws?name=gordon&role=commissioner
@app.websocket("/ws")
async def handler(websocket):
    name = websocket.query_params.get("name")
    role = websocket.query_params.get("role")

    if name == "gordon" and role == "commissioner":
        await websocket.broadcast("Gordon authorized!")

    while True:
        msg = await websocket.receive_text()
        await websocket.send_text(f"Hello {name}: {msg}")

Easy Access Query Parameters

Instead of manually calling websocket.query_params.get(...), you can declare typed query parameters directly in your handler, on_connect, and on_close signatures. Robyn will automatically resolve and coerce them — just like HTTP easy access parameters.

Parameters with defaults are optional. Parameters without defaults are required — if missing, the connection is rejected with an error message.

Easy Access Query Params

WebSocket
/ws?room=chat&page=5
@app.websocket("/ws")
async def handler(websocket, room: str = "default", page: int = 1):
    try:
        while True:
            msg = await websocket.receive_text()
            await websocket.send_text(
                f"room={room} page={page} msg={msg}"
            )
    except WebSocketDisconnect:
        pass

Closing Connections

To programmatically close a WebSocket connection from the server side, use websocket.close(). This will:

  1. Close the WebSocket connection.
  2. Remove the client from the WebSocket registry.
  3. Cause any pending receive_text() to raise WebSocketDisconnect.

Close Connection

WebSocket
/ws
@app.websocket("/ws")
async def handler(websocket):
    while True:
        msg = await websocket.receive_text()
        if msg == "quit":
            await websocket.close()
            break
        await websocket.send_text(f"Got: {msg}")

Connect and Close Callbacks

You can attach optional on_connect and on_close callbacks to your WebSocket handler. These are decorators on the handler function itself.

  • on_connect is called when a new client connects. Its return value is sent to the client as the first message.
  • on_close is called when the connection closes. Its return value is sent to the client as the final message.

Both callbacks receive a websocket object with access to id and query_params. Both are optional.

Callbacks

WebSocket
/chat
@app.websocket("/chat")
async def chat(websocket):
    while True:
        msg = await websocket.receive_text()
        await websocket.broadcast(msg)

@chat.on_connect
def on_connect(websocket):
    return f"Welcome, {websocket.id}!"

@chat.on_close
def on_close(websocket):
    return "Goodbye!"

WebSocket API Reference

The websocket object passed to handlers exposes the following methods and properties:

Method / PropertyDescription

| await websocket.receive_text() | Block until next message; raises WebSocketDisconnect on close | | await websocket.receive_bytes() | Block until next binary message; raises WebSocketDisconnect on close | | await websocket.receive_json() | Same as receive_text() but JSON-decoded | | await websocket.send_text(data) | Send string to this client | | await websocket.send_bytes(data) | Send binary data to this client | | await websocket.send_json(data) | Send JSON to this client | | await websocket.broadcast(data) | Send to all clients on this endpoint | | await websocket.close() | Close the connection server-side | | websocket.id | Connection UUID string | | websocket.query_params | Query parameters from the connection URL |

What's next?

As the codebase grew, Batman wanted to onboard the justice league to help him manage the application.

Robyn told him about the different ways he could scale his application, and how to use views and subrouters to make his code more readable.