Automated AsyncAPI Docs in Django: WebSocket Schemas Like Swagger
Swagger-like docs for REST are easy. For WebSockets, it’s usually a README and wishful thinking.
If you’re building Django WebSocket APIs, AsyncAPI is the Swagger equivalent — you just need automated generation, not manual YAML.
This post shows how to automate AsyncAPI docs in Django so they feel like Swagger: schema-driven, discoverable, and always in sync with your handlers.
You’ll end up with: - A live HTML docs page - JSON/YAML spec endpoints - Automatic schema generation tied to your handlers
Requirements (and what’s optional)
You only need three pieces:
- Django (routing + templates)
- A WebSocket layer (Channels or your own consumer abstraction)
- AsyncAPI renderer (official React component)
Pydantic is optional. It’s great for validation + JSON Schema, but any schema system works as long as you can emit JSON Schema.
What we’re automating
We want three things:
1) A decorator to register WebSocket message handlers 2) A generator that produces AsyncAPI JSON/YAML 3) A docs page that renders a live AsyncAPI UI
The model is simple: annotate handlers with request/response schemas, then auto-generate docs from those schemas.
1) Define message schemas
Use Pydantic (or any schema system) to define payloads. These become AsyncAPI message schemas.
from pydantic import BaseModel
class SubscribeRequest(BaseModel):
channel: str
class PriceUpdate(BaseModel):
channel: str
price: float
ts: int
2) Add a decorator + registry
This decorator does two jobs: - validates payloads at runtime - registers metadata for docs
import functools
from pydantic import BaseModel
class MessageHandler:
def __init__(self, message_type, func, request_schema=None, response_schema=None, description=None, tags=None):
self.message_type = message_type
self.func = func
self.request_schema = request_schema
self.response_schema = response_schema
self.description = description or (func.__doc__ or "")
self.tags = tags or []
functools.update_wrapper(self, func)
class AsyncAPIRegistry:
handlers = {} # consumer_name -> message_type -> MessageHandler
@classmethod
def register_handler(cls, consumer, message_type, handler):
cls.handlers.setdefault(consumer, {})[message_type] = handler
def message_handler(message_type, request_schema=None, response_schema=None, description=None, tags=None):
def decorator(func):
handler = MessageHandler(message_type, func, request_schema, response_schema, description, tags)
handler._is_message_handler = True
handler._message_type = message_type
return handler
return decorator
You can also add a @register_consumer class decorator to auto-register all handlers in a consumer class.
3) Generate AsyncAPI from the registry
At doc time, scan all registered handlers and build an AsyncAPI spec:
from urllib.parse import urlparse
class AsyncAPIGenerator:
@classmethod
def generate_spec(cls, title, version, description, server_url):
parsed = urlparse(server_url)
host = f"{parsed.hostname}:{parsed.port}" if parsed.port else parsed.hostname
spec = {
"asyncapi": "3.0.0",
"info": {"title": title, "version": version, "description": description},
"servers": {"production": {"host": host, "protocol": parsed.scheme or "wss", "pathname": parsed.path or "/"}},
"channels": {},
"operations": {},
"components": {"schemas": {}, "messages": {}},
}
for consumer_name, handlers in AsyncAPIRegistry.handlers.items():
channel = spec["channels"].setdefault(consumer_name, {
"address": f"/{consumer_name}",
"description": f"{consumer_name} WebSocket channel",
"messages": {},
})
for message_type, handler in handlers.items():
if handler.request_schema:
cls._add_schema(spec, handler.request_schema, handler)
channel["messages"][handler.request_schema.__name__] = {
"$ref": f"#/components/messages/{handler.request_schema.__name__}"
}
if handler.response_schema:
cls._add_schema(spec, handler.response_schema, handler)
channel["messages"][handler.response_schema.__name__] = {
"$ref": f"#/components/messages/{handler.response_schema.__name__}"
}
return spec
Then convert to YAML or JSON as needed.
4) Django views for HTML / JSON / YAML
Expose three endpoints:
from django.http import HttpResponse
from django.template.loader import render_to_string
import json
def asyncapi_html(request):
spec = AsyncAPIGenerator.generate_spec(
title="WebSocket API",
version="1.0.0",
description="Realtime API",
server_url=request.build_absolute_uri("/ws"),
)
html = render_to_string("websocket/asyncapi.html", {"spec_json": json.dumps(spec), "title": spec["info"]["title"]})
return HttpResponse(html)
def asyncapi_json(request):
spec = AsyncAPIGenerator.generate_spec(...)
return HttpResponse(json.dumps(spec, indent=2), content_type="application/json")
5) Render a Swagger-like UI
Use the official AsyncAPI React component to render docs in HTML:
<link rel="stylesheet" href="https://unpkg.com/@asyncapi/react-component@latest/styles/default.min.css">
<script src="https://unpkg.com/@asyncapi/react-component@latest/browser/standalone/index.js"></script>
<div id="asyncapi"></div>
<script>
AsyncApiStandalone.render({
schema: {{ spec_json|safe }},
config: { show: { sidebar: true } }
}, document.getElementById('asyncapi'));
</script>
This gives you a fully interactive UI with channels, messages, and schemas.
Practical tips
- Cache the spec in production (15 min is plenty)
- Add tags in the decorator to group operations
- Use examples in Pydantic models to improve docs
- Keep message names stable (clients will depend on them)
Why this automation works
AsyncAPI is the WebSocket equivalent of Swagger. The missing piece is automated generation from code. With a registry + schemas, the docs stay accurate with almost zero manual maintenance.
Final takeaway
If you already use Django for WebSockets, this is the minimum viable way to get Swagger-grade AsyncAPI docs without hand-editing YAML. Once the registry is in place, your docs stay in sync as the code evolves automatically.
Next steps
- Add auth requirements per channel
- Expose both AsyncAPI 2.6 and 3.0
- Generate specs as part of CI to detect breaking changes
If you already use Django for WebSockets, this is the simplest path to Swagger-grade docs.