AsyncAPI for WebSockets
DaloyJS already turns every HTTP route into an OpenAPI 3.1 operation. The same contract-first story extends to your real-time surfaces: the @daloyjs/core/asyncapi module emits a standards-compliant AsyncAPI 3.0 document for the WebSocket routes you register with app.ws(). It is built-in and dependency-free, the same posture as the OpenAPI generator, so it adds nothing to your runtime footprint.
Each app.ws() route becomes one AsyncAPI channel (the socket address plus any path parameters) and one or more operations:
- a
receiveoperation for messages the server receives from clients (always emitted, because clients can send messages after the upgrade), and - an optional
sendoperation for messages the server pushes to clients (emitted only when you declare an outbound schema).
- 01app.ws("/chat/:room")+ optional meta block
- 02generateAsyncAPI(app)built-in, dependency-free
- 03AsyncAPI 3.0 documentchannels + operations
- 04receive / send opsreceive always, send when declared
- 05UI / YAML / codegenGET /asyncapi, asyncapiToYAML, CLI
Quick start
Call generateAsyncAPI(app, options) and you get a plain, JSON-serializable AsyncAPI document. Hand it to AsyncAPI Studio, write it to disk for codegen, or serve it from a route.
Serving an interactive UI
You don't have to wire the spec up yourself. Set asyncapi: true on the app and DaloyJS auto-mounts the AsyncAPI surface, the WebSocket counterpart to docs: true for OpenAPI (Scalar / Swagger / Redoc):
GET /asyncapi: an interactive UI that renders the official AsyncAPI React component, loaded from a CDN via a<script>tag exactly like the OpenAPI viewers (no build step, no extra runtime dependency).GET /asyncapi.jsonandGET /asyncapi.yaml: the AsyncAPI 3.0 document, generated lazily soapp.ws()routes registered afterwards are included.
Like the Scalar/Swagger docs link, surface the URL in your startup banner so it shows up in the terminal:
The UI page ships the same hardened response as the OpenAPI docs: a strict Content-Security-Policy that only allows the CDN asset origin (jsDelivr by default) plus connect-src 'self' so the component can fetch the served spec, nosniff, and no-referrer. Pin Subresource Integrity hashes or point at self-hosted assets via the assets option (asyncapiScriptUrl / asyncapiScriptIntegrity / asyncapiStyleUrl / asyncapiStyleIntegrity) for supply-chain hardening, exactly as with the OpenAPI docs UIs. Use asyncapi: "auto" to mount everywhere except production.
The WebSocket route itself keeps the normal production guardrails. If you run with secure defaults in production, each app.ws() route still needs an allowedOrigins policy or an explicit acknowledgeCrossOriginUpgrade: true. AsyncAPI generation is descriptive; it never relaxes Cross-Site WebSocket Hijacking defenses.
Describing the messages
WebSocket handlers accept an optional meta block that mirrors the HTTP route meta. It is purely descriptive (it never changes the RFC 6455 handshake or runtime behavior), and the AsyncAPI generator reads it to fill in summaries, tags, and message payloads.
summary/description/tags: surfaced on the generated channel and operations.receive: a Standard Schema describing messages the server receives from clients. Falls back to the handler'srequest.bodyschema (the same schema used for payload-size checks).send: a Standard Schema describing messages the server sends to clients. Adds asendoperation when present.operationId: overrides the channel key that is otherwise derived from the path.
Schemas that expose a toJSONSchema() method are converted to JSON Schema for the message payload. Zod 4 schemas expose that method directly; other validators may need the same adapter they use for OpenAPI output. Anything else still validates at runtime, but the generated AsyncAPI payload falls back to a permissive {} placeholder rather than throwing.
Generated document shape
A single app.ws("/chat/:room", ...) route with the meta above produces roughly:
YAML output
asyncapiToYAML(doc) renders the document as YAML 1.2 using the same dependency-free emitter shared with the OpenAPI generator.
CLI
The daloy inspect command can print the AsyncAPI document for any app it can load, mirroring --openapi. Use --format yaml (or --yaml) for YAML output.
Notes
- When the app has no WebSocket routes the document still validates, with empty
channelsandoperationsmaps. - Channel keys are derived from the path (
/chat/:room/feed→chatRoomFeed); collisions are de-duplicated with a numeric suffix. Setmeta.operationIdfor a stable, explicit key. - The generator is read-only: it never mounts a route or changes your socket's security posture. See the WebSocket primitives page for the CSWSH refuse-to-boot guards.