Skip to content
Huy Nguyen's Blog
Go back

ChanX: Structured, Type-Safe WebSockets for Django and FastAPI

After years of building realtime applications — group messaging, voice pipelines, streaming AI assistants — I kept writing the same WebSocket boilerplate in every project. Whether in Django Channels or FastAPI, it always looked like this:

# The usual mess
async def receive_json(self, content):
    action = content.get("action")
    if action == "chat":
        # manual validation, no types
        await self.handle_chat(content)
    elif action == "ping":
        await self.handle_ping(content)
    elif action == "join_room":
        await self.handle_join_room(content)
    # ... 20 more elif statements

Manual routing chains. Hand-written validation. Runtime type surprises. Zero documentation — no source of truth for what messages are accepted or returned, even though the AsyncAPI standard exists. Testing that’s timing-based and unpredictable. Painful code reviews, because you can’t tell what a consumer does without reading it end to end.

REST APIs solved all of this years ago with DRF and FastAPI: validation, type safety, auto-generated docs. ChanX brings that same developer experience to WebSockets.

Table of contents

Open Table of contents

The same code, structured

@channel(name="chat", description="Real-time chat API")
class ChatConsumer(AsyncJsonWebsocketConsumer):
    groups = ["chat_room"]  # Auto-join this group on connect

    @ws_handler(
        summary="Handle chat messages",
        output_type=ChatNotificationMessage,
    )
    async def handle_chat(self, message: ChatMessage) -> None:
        # Automatically routed, validated, and type-safe
        await self.broadcast_message(
            ChatNotificationMessage(payload=message.payload)
        )

    @ws_handler
    async def handle_ping(self, message: PingMessage) -> PongMessage:
        return PongMessage()  # Auto-documented in AsyncAPI

Messages are Pydantic models with a discriminated action field — incoming messages are validated and routed to the right handler automatically. No if-else chains, no manual validation, and mypy/pyright catch mistakes before runtime.

What you get

Broadcasting from anywhere

# From a Django view, Celery task, or FastAPI endpoint
ChatConsumer.broadcast_event_sync(
    NotificationEvent(payload={"alert": "System maintenance"}),
    groups=["admin_users"],
)

Where it shines

I built ChanX from what I wished I had on my own projects, and I use it in production daily.

Feedback, issues, and contributions are all welcome.


Share this post:


Previous Post
DRF Auth Kit: A Complete Authentication Solution for Django REST Framework
Next Post
fast-channels: Django Channels for FastAPI