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
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
}
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
Send options:
compress: Enable compression (boolean)
close()
Closes the WebSocket connection.
close(code?: number, reason?: string): void
Close status code (default: 1000)
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)
}
}))
)
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.