Skip to main content
The WebSocket Helper provides a platform-agnostic API for handling WebSocket connections in Hono applications.

Import

import { defineWebSocketHelper, WSContext } from 'hono/websocket'
import type { UpgradeWebSocket, WSEvents, WSReadyState } from 'hono/websocket'

Core Concepts

The WebSocket helper uses an adapter pattern to support different runtime environments (Cloudflare Workers, Deno, Bun, Node.js).

Functions

defineWebSocketHelper()

Creates a platform-specific WebSocket upgrade function.
function defineWebSocketHelper<T = unknown, U = any>(
  handler: WebSocketHelperDefineHandler<T, U>
): UpgradeWebSocket<T, U>
handler
WebSocketHelperDefineHandler<T, U>
required
Function that handles the WebSocket upgrade for a specific platform.
(c: Context, events: WSEvents<T>, options?: U) => Promise<Response | void> | Response | void
return
UpgradeWebSocket<T, U>
An upgrade function that can be used as middleware or called directly
Example
import { defineWebSocketHelper } from 'hono/websocket'

// Define platform-specific WebSocket handler
const upgradeWebSocket = defineWebSocketHelper((c, events, options) => {
  // Platform-specific upgrade logic here
  // This example is simplified - see runtime-specific implementations
  
  const { socket, response } = upgradeToWebSocket(c.req.raw)
  
  socket.onopen = (evt) => events.onOpen?.(evt, new WSContext({
    send: (data) => socket.send(data),
    close: (code, reason) => socket.close(code, reason),
    readyState: socket.readyState,
    raw: socket
  }))
  
  return response
})

export { upgradeWebSocket }

Classes

WSContext

Provides a unified API for controlling WebSocket connections.
class WSContext<T = unknown> {
  constructor(init: WSContextInit<T>)
  
  send(source: string | ArrayBuffer | Uint8Array, options?: SendOptions): void
  close(code?: number, reason?: string): void
  
  get readyState(): WSReadyState
  raw?: T
  url: URL | null
  protocol: string | null
  binaryType: BinaryType
}
init
WSContextInit<T>
required
Initialization object:
  • send: Function to send data
  • close: Function to close connection
  • readyState: Current connection state
  • raw: Optional raw WebSocket object
  • url: Optional WebSocket URL
  • protocol: Optional WebSocket protocol
Methods:

send()

Sends data through the WebSocket.
send(source: string | ArrayBuffer | Uint8Array, options?: SendOptions): void
source
string | ArrayBuffer | Uint8Array
required
Data to send
options
SendOptions
Send options:
  • compress: Enable compression (boolean)

close()

Closes the WebSocket connection.
close(code?: number, reason?: string): void
code
number
Close status code (default: 1000)
reason
string
Close reason message
Properties:
  • readyState: Current state (0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED)
  • raw: Access to platform-specific WebSocket object
  • url: WebSocket connection URL
  • protocol: Selected WebSocket sub-protocol
  • binaryType: Binary data type (default: ‘arraybuffer’)

Types

WSEvents

Event handlers for WebSocket lifecycle.
interface WSEvents<T = unknown> {
  onOpen?: (evt: Event, ws: WSContext<T>) => void
  onMessage?: (evt: MessageEvent<WSMessageReceive>, ws: WSContext<T>) => void
  onClose?: (evt: CloseEvent, ws: WSContext<T>) => void
  onError?: (evt: Event, ws: WSContext<T>) => void
}
onOpen
(evt: Event, ws: WSContext<T>) => void
Called when connection is established
onMessage
(evt: MessageEvent, ws: WSContext<T>) => void
Called when message is received
onClose
(evt: CloseEvent, ws: WSContext<T>) => void
Called when connection is closed
onError
(evt: Event, ws: WSContext<T>) => void
Called when an error occurs

UpgradeWebSocket

Type for the WebSocket upgrade function.
interface UpgradeWebSocket<T = unknown, U = any> {
  // As middleware
  (
    createEvents: (c: Context) => WSEvents<T> | Promise<WSEvents<T>>,
    options?: U
  ): MiddlewareHandler
  
  // Direct call
  (
    c: Context,
    events: WSEvents<T>,
    options?: U
  ): Promise<Response & TypedResponse<{}, StatusCode, 'ws'>>
}

WSReadyState

WebSocket connection state.
type WSReadyState = 0 | 1 | 2 | 3
// 0 = CONNECTING
// 1 = OPEN
// 2 = CLOSING
// 3 = CLOSED

Usage with Runtime Adapters

Cloudflare Workers

import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/cloudflare-workers'

const app = new Hono()

app.get(
  '/ws',
  upgradeWebSocket((c) => {
    return {
      onOpen: (evt, ws) => {
        console.log('Connection opened')
        ws.send('Hello from server!')
      },
      onMessage: (evt, ws) => {
        console.log('Received:', evt.data)
        ws.send(`Echo: ${evt.data}`)
      },
      onClose: (evt, ws) => {
        console.log('Connection closed')
      },
      onError: (evt, ws) => {
        console.error('WebSocket error')
      }
    }
  })
)

export default app

Deno

import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/deno'

const app = new Hono()

app.get(
  '/ws',
  upgradeWebSocket((c) => ({
    onMessage: (evt, ws) => {
      ws.send(`You sent: ${evt.data}`)
    }
  }))
)

Deno.serve(app.fetch)

Bun

import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/bun'

const app = new Hono()

app.get(
  '/ws',
  upgradeWebSocket((c) => ({
    onMessage: (evt, ws) => {
      ws.send(`Echo: ${evt.data}`)
    }
  }))
)

export default app

Examples

Chat Room

import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/cloudflare-workers'

const app = new Hono()

const connections = new Set<WSContext>()

app.get(
  '/chat',
  upgradeWebSocket((c) => {
    return {
      onOpen: (evt, ws) => {
        connections.add(ws)
        // Broadcast join message
        connections.forEach(conn => {
          if (conn !== ws) {
            conn.send('A user joined the chat')
          }
        })
      },
      
      onMessage: (evt, ws) => {
        // Broadcast message to all connections
        const message = evt.data
        connections.forEach(conn => {
          conn.send(message)
        })
      },
      
      onClose: (evt, ws) => {
        connections.delete(ws)
        // Broadcast leave message
        connections.forEach(conn => {
          conn.send('A user left the chat')
        })
      }
    }
  })
)

Real-time Data Stream

import { upgradeWebSocket } from 'hono/cloudflare-workers'

app.get(
  '/stream',
  upgradeWebSocket((c) => {
    let interval: ReturnType<typeof setInterval>
    
    return {
      onOpen: (evt, ws) => {
        // Send data every second
        interval = setInterval(() => {
          if (ws.readyState === 1) { // OPEN
            ws.send(JSON.stringify({
              timestamp: Date.now(),
              data: Math.random()
            }))
          }
        }, 1000)
      },
      
      onClose: () => {
        clearInterval(interval)
      }
    }
  })
)

Authenticated WebSocket

import { upgradeWebSocket } from 'hono/cloudflare-workers'

app.get(
  '/ws',
  upgradeWebSocket(async (c) => {
    // Authenticate before upgrade
    const token = c.req.header('Authorization')
    const user = await verifyToken(token)
    
    if (!user) {
      return c.text('Unauthorized', 401)
    }
    
    return {
      onOpen: (evt, ws) => {
        ws.send(`Welcome ${user.name}!`)
      },
      
      onMessage: (evt, ws) => {
        // Handle authenticated user messages
        console.log(`Message from ${user.name}:`, evt.data)
      }
    }
  })
)

Binary Data

import { upgradeWebSocket } from 'hono/cloudflare-workers'

app.get(
  '/binary',
  upgradeWebSocket((c) => ({
    onMessage: (evt, ws) => {
      if (evt.data instanceof ArrayBuffer) {
        // Handle binary data
        const view = new Uint8Array(evt.data)
        console.log('Received binary data:', view)
        
        // Echo back
        ws.send(evt.data)
      }
    }
  }))
)

Connection State Tracking

import { upgradeWebSocket } from 'hono/cloudflare-workers'

app.get(
  '/ws',
  upgradeWebSocket((c) => ({
    onOpen: (evt, ws) => {
      console.log('State:', ws.readyState) // 1 (OPEN)
      console.log('URL:', ws.url)
      console.log('Protocol:', ws.protocol)
    },
    
    onMessage: (evt, ws) => {
      if (ws.readyState === 1) {
        ws.send('Response')
      }
    },
    
    onClose: (evt, ws) => {
      console.log('Closed with code:', evt.code)
      console.log('Reason:', evt.reason)
    }
  }))
)

Platform Support

The WebSocket helper supports multiple runtimes through platform-specific implementations:
  • Cloudflare Workers: hono/cloudflare-workers
  • Deno: hono/deno
  • Bun: hono/bun
  • Node.js: Via adapters like @hono/node-server
Each platform provides its own upgradeWebSocket function that implements the unified API.

Build docs developers (and LLMs) love