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.

Prev