r/FastAPI • u/huygl99 • 9d ago
Other A code-first approach to auto-generated docs for FastAPI WebSockets (my AsyncAPI Conf 2026 talk)
Hi all,
I recently gave a talk at AsyncAPI Conf 2026 (online), and the demo app was FastAPI, so I thought it fits here.
The short version: I love that a FastAPI route gives you validation, OpenAPI and /docs just from type hints. @app.websocket gives you none of that. You receive_json(), check data["action"], send_json() back, and the only place the message shapes exist is in your head. Then you copy the ConnectionManager from the docs, and it works until you run a second worker haha.
That bit me for real. My backend sent the chat text as content, the React side read body. Both sides' tests passed, nothing threw, the UI just showed an empty space. Took me most of a day.
So in the talk I treat a WebSocket handler like a route, where the signature is the contract:
@channel(name="chat", description="Room chat")
class ChatConsumer(AsyncJsonWebsocketConsumer):
groups = ["lobby"]
@ws_handler(output_type=ChatNotification)
async def handle_chat(self, message: ChatMessage) -> None:
await self.broadcast_message(ChatNotification(payload=message.payload))ChatMessage and ChatNotification are Pydantic models. From handlers like this the app serves an AsyncAPI 3 document plus a docs page (basically /openapi.json and /docs for the socket), and from that document I generate a Python client and a TypeScript client and commit them. When someone renames a field, the frontend type check fails in the PR.
The library is chanx, which I wrote and use at work (open source, BSD, sorry for the plug). Besides the docs, the parts I'd miss most on a plain @app.websocket: a bad payload gets a structured error reply before my handler runs, groups go through Redis (via fast-channels) so broadcasts reach every worker, and I can push to a room from a normal route or a background task with await ChatConsumer.broadcast_event(...). There's also a test communicator, so socket tests don't need sleep(), and chanx-js generates the frontend client with React, Vue, Svelte or Solid bindings.
Talk (jumps to my session): https://youtu.be/-LPCnm3F9Mc?t=7250
Slides: https://drive.google.com/file/d/1nmcMrmSslkzGcMPYxj_QXbJ2a01lLFFc/view?usp=sharing
Demo repo: https://github.com/huynguyengl99/asyncapi-talk-demo
chanx repo: https://github.com/chanx-dev/chanx
Other talks from the conf: https://conference.asyncapi.com/venue/Online
English isn't my first language, so if I explain something badly in the video, ask here and I'll try again. And I'm curious how you handle this today: hand-written docs, a shared types package, or just the ConnectionManager and hope?
