npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@juice10/hono-zod-asyncapi

v0.1.0

Published

A Hono package for generating AsyncAPI specifications for WebSocket routes with Zod validation

Readme

@juice10/hono-zod-asyncapi

A Hono package for generating AsyncAPI specifications for WebSocket routes with Zod validation.

Features

  • 🚀 AsyncAPI 3.0 Support - Generate AsyncAPI 3.0 compliant specifications
  • 🔌 WebSocket Integration - Built specifically for WebSocket channels
  • Zod Validation - Use Zod schemas for message validation and type safety
  • 📝 Type Safety - Full TypeScript support with automatic type inference
  • 🎯 Easy to Use - Simple API similar to @hono/zod-openapi
  • 📚 Documentation Generation - Auto-generate API documentation from your code
  • 🔄 OpenAPI Integration - Works seamlessly with @hono/zod-openapi for unified REST + WebSocket APIs

Installation

npm install @juice10/hono-zod-asyncapi hono zod

Quick Start

import { AsyncAPIHono, createChannel, z } from '@juice10/hono-zod-asyncapi'

// Create an AsyncAPI-enabled Hono app
const app = new AsyncAPIHono({
  info: {
    title: 'Chat API',
    version: '1.0.0',
    description: 'Real-time chat API using WebSockets',
  },
  servers: {
    development: {
      host: 'localhost:3000',
      protocol: 'ws',
      description: 'Development server',
    },
  },
})

// Define a channel with Zod schemas
const chatChannel = createChannel({
  path: '/chat/:roomId',
  description: 'Real-time chat room',
  send: {
    payload: z.object({
      message: z.string(),
      userId: z.string(),
      timestamp: z.number(),
    }),
    description: 'Send a chat message to the room',
  },
  receive: {
    payload: z.object({
      message: z.string(),
      userId: z.string(),
      timestamp: z.number(),
    }),
    description: 'Receive chat messages from the room',
  },
  tags: ['chat'],
})

// Register the channel with a handler
app.channel('chatRoom', chatChannel, (ws, message, ctx) => {
  console.log(`Message in room ${ctx.params.roomId}:`, message)

  // Send a response
  ws.send({
    message: `Echo: ${message.message}`,
    userId: 'system',
    timestamp: Date.now(),
  })
})

// Serve the AsyncAPI documentation
app.doc('/asyncapi.json')

export default app

Integration with @hono/zod-openapi

Combine REST APIs (OpenAPI) and WebSocket APIs (AsyncAPI) in a single application:

npm install @juice10/hono-zod-asyncapi @hono/zod-openapi hono zod

Option 1: Merge Separate Apps

import { OpenAPIHono, createRoute, z as openAPIZ } from '@hono/zod-openapi'
import { AsyncAPIHono, createChannel, z, mergeAPIDocs } from '@juice10/hono-zod-asyncapi'

// Create REST API with OpenAPI
const restAPI = new OpenAPIHono()

const getUserRoute = createRoute({
  method: 'get',
  path: '/api/users/:id',
  responses: {
    200: {
      content: {
        'application/json': {
          schema: openAPIZ.object({
            id: openAPIZ.string(),
            name: openAPIZ.string(),
          }),
        },
      },
      description: 'User details',
    },
  },
})

restAPI.openapi(getUserRoute, (c) => {
  return c.json({ id: '123', name: 'Alice' })
})

// Create WebSocket API with AsyncAPI
const wsAPI = new AsyncAPIHono({
  info: {
    title: 'WebSocket API',
    version: '1.0.0',
  },
})

const chatChannel = createChannel({
  path: '/ws/chat/:roomId',
  send: {
    payload: z.object({
      message: z.string(),
      userId: z.string(),
    }),
  },
})

wsAPI.channel('chat', chatChannel, (ws, message, ctx) => {
  console.log(`Chat message in room ${ctx.params.roomId}`)
  ws.send({ message: 'Hello!', userId: 'system' })
})

// Merge both APIs
const app = mergeAPIDocs(restAPI, wsAPI)

// Serve both documentations
app.doc('/api/openapi.json')         // OpenAPI spec for REST
app.asyncapiDoc('/api/asyncapi.json') // AsyncAPI spec for WebSocket

export default app

Option 2: Unified API App

import { UnifiedAPIHono, createChannel, z } from '@juice10/hono-zod-asyncapi'

const app = new UnifiedAPIHono({
  openapi: {
    info: { title: 'My API', version: '1.0.0' },
  },
  asyncapi: {
    info: { title: 'My WebSocket API', version: '1.0.0' },
  },
})

// Add WebSocket channels
const channel = createChannel({
  path: '/ws/events',
  send: { payload: z.object({ event: z.string() }) },
})

app.channel('events', channel)

// Serve both docs
app.docs('/api/openapi.json', '/api/asyncapi.json')

Unified Configuration Helpers

import { createUnifiedInfo, createUnifiedServers } from '@juice10/hono-zod-asyncapi'

// Create consistent info for both specs
const info = createUnifiedInfo({
  title: 'My API',
  version: '1.0.0',
  description: 'REST and WebSocket API',
  contact: {
    name: 'API Team',
    email: '[email protected]',
  },
})

// Create server configs for both HTTP and WebSocket
const servers = createUnifiedServers({
  http: {
    url: 'https://api.example.com',
    description: 'Production HTTP server',
  },
  ws: {
    host: 'ws.example.com',
    protocol: 'wss',
    description: 'Production WebSocket server',
  },
})

// Use in your apps
const restAPI = new OpenAPIHono({ openapi: { ...info.openapi, servers: servers.openapi } })
const wsAPI = new AsyncAPIHono({ info: info.asyncapi, servers: servers.asyncapi })

API Reference

AsyncAPIHono

The main class that extends Hono with AsyncAPI support.

const app = new AsyncAPIHono({
  info: {
    title: 'My API',
    version: '1.0.0',
    description: 'API description',
  },
  servers: {
    production: {
      host: 'api.example.com',
      protocol: 'wss',
      description: 'Production server',
    },
  },
})

Methods

  • channel(id, config, handler?) - Register a WebSocket channel
  • doc(path, options?) - Serve AsyncAPI document as JSON
  • docYAML(path, options?) - Serve AsyncAPI document as YAML
  • getAsyncAPIDocument(options?) - Get the AsyncAPI document object
  • route(path, app) - Merge routes from another AsyncAPIHono instance
  • basePath(path) - Create a new instance with a base path

createChannel

Factory function for creating type-safe channel configurations.

const channel = createChannel({
  path: '/events/:eventId',
  description: 'Event stream',
  send: {
    payload: z.object({ type: z.string(), data: z.any() }),
    description: 'Send event data',
  },
  receive: {
    payload: z.object({ type: z.string(), data: z.any() }),
    description: 'Receive event updates',
  },
  parameters: {
    eventId: z.string().uuid(),
  },
  tags: ['events'],
})

Channel Handler

Type-safe handler for WebSocket messages:

type ChannelHandler = (
  ws: WebSocketData,
  message: InferredMessagePayload,
  context: ChannelContext
) => void | Promise<void>

WebSocketData

  • send(data) - Send a message (validated against send schema)
  • close(code?, reason?) - Close the WebSocket connection

ChannelContext

  • params - Path parameters (typed based on channel path)
  • headers - Request headers

Examples

Basic WebSocket Channel

import { AsyncAPIHono, createChannel, z } from '@juice10/hono-zod-asyncapi'

const app = new AsyncAPIHono()

const pingChannel = createChannel({
  path: '/ping',
  description: 'Ping-pong channel',
  receive: {
    payload: z.object({ ping: z.string() }),
  },
  send: {
    payload: z.object({ pong: z.string() }),
  },
})

app.channel('ping', pingChannel, (ws, message) => {
  ws.send({ pong: message.ping })
})

Channel with Path Parameters

const userChannel = createChannel({
  path: '/users/:userId/notifications',
  description: 'User-specific notification channel',
  send: {
    payload: z.object({
      type: z.enum(['info', 'warning', 'error']),
      message: z.string(),
    }),
  },
})

app.channel('userNotifications', userChannel, (ws, message, ctx) => {
  console.log(`Notification for user ${ctx.params.userId}`)
  // Handler logic
})

Complex Message Schemas

const gameChannel = createChannel({
  path: '/game/:gameId',
  description: 'Real-time game state updates',
  send: {
    payload: z.discriminatedUnion('type', [
      z.object({
        type: z.literal('move'),
        playerId: z.string(),
        position: z.object({ x: z.number(), y: z.number() }),
      }),
      z.object({
        type: z.literal('chat'),
        playerId: z.string(),
        message: z.string(),
      }),
    ]),
  },
  receive: {
    payload: z.object({
      gameState: z.object({
        players: z.array(z.any()),
        status: z.enum(['waiting', 'playing', 'finished']),
      }),
    }),
  },
})

Modular Architecture

// channels/chat.ts
export const chatRoutes = new AsyncAPIHono()

chatRoutes.channel('publicChat', publicChatChannel, handler1)
chatRoutes.channel('privateChat', privateChatChannel, handler2)

// channels/notifications.ts
export const notificationRoutes = new AsyncAPIHono()

notificationRoutes.channel('userNotifications', notificationChannel, handler)

// main.ts
const app = new AsyncAPIHono({
  info: {
    title: 'My API',
    version: '1.0.0',
  },
})

app.route('/chat', chatRoutes)
app.route('/notifications', notificationRoutes)
app.doc('/asyncapi.json')

Custom Server Configuration

import { createWebSocketServer, createSecureWebSocketServer } from '@juice10/hono-zod-asyncapi'

const app = new AsyncAPIHono({
  info: {
    title: 'Multi-Environment API',
    version: '1.0.0',
  },
  servers: {
    development: createWebSocketServer('localhost:3000', 'Development server'),
    staging: createSecureWebSocketServer('staging.api.example.com', 'Staging server'),
    production: createSecureWebSocketServer('api.example.com', 'Production server'),
  },
})

AsyncAPI Specification

This package generates AsyncAPI 3.0 compliant specifications. The generated document includes:

  • Channels - WebSocket endpoints with their addresses
  • Operations - Send and receive operations for each channel
  • Messages - Message schemas with Zod-validated payloads
  • Components - Reusable schemas and message definitions

Example Generated Spec

{
  "asyncapi": "3.0.0",
  "info": {
    "title": "Chat API",
    "version": "1.0.0"
  },
  "servers": {
    "development": {
      "host": "localhost:3000",
      "protocol": "ws"
    }
  },
  "channels": {
    "chatRoom": {
      "address": "/chat/{roomId}",
      "messages": {
        "chatRoom_send_message": {
          "$ref": "#/components/messages/chatRoom_send_message"
        }
      }
    }
  },
  "operations": {
    "send_chatRoom_0": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/chatRoom"
      },
      "messages": [
        {
          "$ref": "#/components/messages/chatRoom_send_message"
        }
      ]
    }
  },
  "components": {
    "messages": {
      "chatRoom_send_message": {
        "payload": {
          "$ref": "#/components/schemas/schema_0"
        }
      }
    },
    "schemas": {
      "schema_0": {
        "type": "object",
        "properties": {
          "message": { "type": "string" },
          "userId": { "type": "string" }
        },
        "required": ["message", "userId"]
      }
    }
  }
}

TypeScript Support

Full TypeScript support with automatic type inference:

// Types are automatically inferred from Zod schemas
const channel = createChannel({
  path: '/data',
  send: {
    payload: z.object({
      value: z.number(),
      label: z.string(),
    }),
  },
})

app.channel('data', channel, (ws, message, ctx) => {
  // message is typed as { value: number; label: string }
  const num: number = message.value
  const str: string = message.label

  // ws.send() expects the correct type
  ws.send({ value: 42, label: 'Answer' }) // ✓ Valid
  ws.send({ value: '42', label: 'Answer' }) // ✗ Type error
})

Runtime Support

This package is designed to work with any JavaScript runtime that supports Hono. However, actual WebSocket handling depends on the runtime:

  • Bun - Native WebSocket support
  • Cloudflare Workers - WebSocket support via Durable Objects
  • Node.js - Requires WebSocket library (ws, uWebSockets.js, etc.)
  • Deno - Native WebSocket support

License

MIT

Credits

Inspired by @hono/zod-openapi by the Hono team.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.