Relevant source files
The following files were used as context for generating this wiki page:
- src/adapter/bun/websocket.ts
- src/helper/streaming/sse.ts
- src/adapter/cloudflare-workers/websocket.ts
- src/adapter/deno/websocket.ts
- src/adapter/aws-lambda/handler.ts
- src/jsx/streaming.ts
- src/client/client.ts
- src/helper/websocket/index.ts
- src/helper/streaming/stream.ts
- src/helper/proxy/index.ts
- src/adapter/deno/deno.d.ts
- src/utils/stream.ts
- src/helper/streaming/text.ts
- runtime-tests/workerd/index.ts
- src/adapter/lambda-edge/handler.ts
- src/helper/adapter/index.ts
- src/utils/ipaddr.ts
- src/adapter/netlify/mod.ts
- src/adapter/cloudflare-pages/handler.ts
- src/helper/factory/index.ts
- src/adapter/cloudflare-workers/index.ts
- src/adapter/cloudflare-workers/conninfo.ts
- src/adapter/bun/conninfo.ts
- src/adapter/cloudflare-workers/serve-static-module.ts
- src/helper/streaming/utils.ts
- src/hono-base.ts
- src/adapter/vercel/conninfo.ts
- src/adapter/bun/index.ts
- src/middleware/etag/digest.ts
- src/adapter/lambda-edge/conninfo.ts
Overview
WebSocket support in Hono provides a unified, cross-runtime abstraction for establishing, managing, and interacting with persistent bidirectional socket connections. Built around the core WSContext class and defineWebSocketHelper builder, the helper architecture normalizes event lifecycles such as onOpen, onMessage, onClose, and onError alongside message sending and connection termination across different server environments. Runtime-specific adapters like Cloudflare Workers and Bun handle low-level handshake protocols, socket pair generation, and event bridge delegation seamlessly.
Sources: src/adapter/bun/websocket.ts:33-102, src/adapter/cloudflare-workers/websocket.ts:5-52, src/helper/websocket/index.ts:14-91, src/helper/websocket/index.ts:111-139
WebSocket helper architecture and interfaces
Core Contracts and Event Types
The WebSocket helper exposes public TypeScript interfaces and type definitions that govern connection upgrades, message payloads, and socket event callbacks. The WSEvents interface defines four optional lifecycle listeners parameterized by a generic type T representing the raw underlying socket reference: onOpen, onMessage, onClose, and onError. Each listener receives runtime event objects alongside a normalized WSContext<T> instance.
export 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
}Sources: src/helper/websocket/index.ts:14-19
Connection readiness is tracked using the numeric WSReadyState union type corresponding to standard WebSocket lifecycle states. Message payloads accepted during transmission or received via events conform to WSMessageReceive.
| ReadyState Value | State Name | Description |
|---|---|---|
0 | CONNECTING | The connection has not yet been established. |
1 | OPEN | The connection is open and ready to communicate. |
2 | CLOSING | The connection is in the process of closing. |
3 | CLOSED | The connection is closed or could not be opened. |
Sources: src/helper/websocket/index.ts:43-45, src/helper/websocket/index.ts:93
Note
WSReadyState maps directly to standard browser and runtime WebSocket ready states (0 through 3), allowing runtime-agnostic status checking inside WSContext.
Sources: src/helper/websocket/index.ts:43-45, src/helper/websocket/index.ts:83-85
Factory Signatures and Upgrade Overloads
The UpgradeWebSocket type signature supports two distinct calling conventions: middleware creation for route handlers, and direct execution taking an active Hono Context. The defineWebSocketHelper factory function implements both overloads, dynamically returning either a Hono MiddlewareHandler or a promise resolving to an upgraded HTTP Response.
export interface UpgradeWebSocket<T = unknown, U = any, _WSEvents = WSEvents<T>> {
(
createEvents: (c: Context) => _WSEvents | Promise<_WSEvents>,
options?: U
): MiddlewareHandler<
any,
string,
{
outputFormat: 'ws'
}
>
(
c: Context,
events: _WSEvents,
options?: U
): Promise<Response & TypedResponse<{}, StatusCode, 'ws'>>
}Sources: src/helper/websocket/index.ts:24-40
When defineWebSocketHelper receives a factory function as its first argument, it inspects arguments at runtime to return a middleware handler that awaits createEvents(c) before executing the underlying upgrade handler and calling next(). When invoked directly with a Context and event map, it executes the handler immediately and throws an error if the upgrade fails.
export const defineWebSocketHelper = <T = unknown, U = any>(
handler: WebSocketHelperDefineHandler<T, U>
): UpgradeWebSocket<T, U> => {
return ((
...args:
| [createEvents: (c: Context) => WSEvents<T> | Promise<WSEvents<T>>, options?: U]
| [c: Context, events: WSEvents<T>, options?: U]
) => {
if (typeof args[0] === 'function') {
const [createEvents, options] = args
return async function upgradeWebSocket(c, next) {
const events = await createEvents(c)
const result = await handler(c, events, options as U)
if (result) {
return result
}
await next()
}
} else {
const [c, events, options] = args as [c: Context, events: WSEvents<T>, options?: U]
return (async () => {
const upgraded = await handler(c, events, options as U)
if (!upgraded) {
throw new Error('Failed to upgrade WebSocket')
}
return upgraded
})()
}
}) as UpgradeWebSocket<T, U>Sources: src/helper/websocket/index.ts:111-139
Warning
Passing an invalid argument signature or failing to return an upgrade response from the handler function causes direct invocations to reject with a Failed to upgrade WebSocket error.
Sources: src/helper/websocket/index.ts:133-135
Normalized context and event abstraction
WSContext Initialization and Properties
The WSContext<T> class wraps runtime-specific socket implementations into a unified control interface. Constructed via a WSContextInit<T> descriptor, WSContext exposes properties for the underlying raw socket reference (raw), the connection URL parsed as a URL object or null, the negotiated subprotocol or null, and the active binary type defaulting to 'arraybuffer'.
export class WSContext<T = unknown> {
#init: WSContextInit<T>
constructor(init: WSContextInit<T>) {
this.#init = init
this.raw = init.raw
this.url = init.url ? new URL(init.url) : null
this.protocol = init.protocol ?? null
}
send(source: string | ArrayBuffer | Uint8Array<ArrayBuffer>, options?: SendOptions): void {
this.#init.send(source, options ?? {})
}
raw?: T
binaryType: BinaryType = 'arraybuffer'
get readyState(): WSReadyState {
return this.#init.readyState
}
url: URL | null
protocol: string | null
close(code?: number, reason?: string) {
this.#init.close(code, reason)
}
}Sources: src/helper/websocket/index.ts:70-91
| Property / Method | Type | Description |
|---|---|---|
raw | T | undefined | The runtime-specific underlying socket instance. |
readyState | WSReadyState | Getter delegating to initialization ready state (0 to 3). |
url | URL | null | Parsed connection URL. |
protocol | string | null | Negotiated subprotocol string. |
binaryType | BinaryType | Configured binary data format, defaulting to 'arraybuffer'. |
send(source, options?) | (source: string | ArrayBuffer | Uint8Array, options?: SendOptions) => void | Transmits text or binary data with optional compression. |
close(code?, reason?) | (code?: number, reason?: string) => void | Terminates the WebSocket connection with optional code and reason. |
Sources: src/helper/websocket/index.ts:70-91
Note
WSContext lazily wraps init.url into a JavaScript URL instance during construction if present, converting raw string endpoints into structured URI objects automatically.
Sources: src/helper/websocket/index.ts:71-76
Event Normalization and Message Wrapping
Incoming raw payloads are normalized into standard browser-compatible event constructs. The helper provides the createWSMessageEvent utility function, which wraps received data streams conforming to WSMessageReceive into standard MessageEvent objects destined for onMessage listeners.
export type WSMessageReceive = string | Blob | ArrayBufferLike
export const createWSMessageEvent = (source: WSMessageReceive): MessageEvent<WSMessageReceive> => {
return new MessageEvent<WSMessageReceive>('message', {
data: source,
})
}Sources: src/helper/websocket/index.ts:93-99
export type WSMessageReceive = string | Blob | ArrayBufferLike
export const createWSMessageEvent = (source: WSMessageReceive): MessageEvent<WSMessageReceive> => {
return new MessageEvent<WSMessageReceive>('message', {
data: source,
})
}Sources: src/helper/websocket/index.ts:93-99
Cloudflare Workers WebSocket upgrade adapter
WebSocketPair Instantiation and Response Handshaking
The Cloudflare Workers upgrade adapter utilizes defineWebSocketHelper to process incoming HTTP requests and establish WebSocket connections. It validates the request by checking if the Upgrade header equals 'websocket', returning early if the precondition fails.
> = defineWebSocketHelper(async (c, events) => {
const upgradeHeader = c.req.header('Upgrade')
if (upgradeHeader !== 'websocket') {
return
}Sources: src/adapter/cloudflare-workers/websocket.ts:10-14
Once validated, the adapter instantiates a global WebSocketPair to yield a client-server socket pair, mapping webSocketPair[0] to the client socket and webSocketPair[1] to the worker server socket. It wraps the server socket inside a WSContext<WebSocket> instance that delegates lifecycle operations such as close, send, readyState, and URL parsing directly to the underlying Workers socket.
// @ts-expect-error WebSocketPair is not typed
const webSocketPair = new WebSocketPair()
const client: WebSocket = webSocketPair[0]
const server: WebSocket = webSocketPair[1]
const wsContext = new WSContext<WebSocket>({
close: (code, reason) => server.close(code, reason),
get protocol() {
return server.protocol
},
raw: server,
get readyState() {
return server.readyState as WSReadyState
},
url: server.url ? new URL(server.url) : null,
send: (source) => server.send(source),
})Sources: src/adapter/cloudflare-workers/websocket.ts:16-32
Note
Cloudflare Workers do not support the 'open' event lifecycle hook, so event listener attachments are restricted to close, message, and error event types.
Sources: src/adapter/cloudflare-workers/websocket.ts:34-44
The server socket accepts incoming connections via server.accept?.(), and the adapter returns a custom Response object with status 101 and the client socket attached under the webSocket property.
// @ts-expect-error - server.accept is not typed
server.accept?.()
return new Response(null, {
status: 101,
// @ts-expect-error - webSocket is not typed
webSocket: client,
})Sources: src/adapter/cloudflare-workers/websocket.ts:46-52
Runtime Testing Harness
The workerd runtime test suite configures a Hono application instance to verify environment bindings, runtime identification via getRuntimeKey(), and WebSocket echo behavior under Cloudflare Workers. The /ws route invokes upgradeWebSocket with a callback factory returning an onMessage handler that echoes received event data back to the client using ws.send(event.data as string).
app.get(
'/ws',
upgradeWebSocket(() => {
return {
onMessage(event, ws) {
ws.send(event.data as string)
},
}
})
)Sources: runtime-tests/workerd/index.ts:15-24
Bun server WebSocket integration
Native Server Upgrade Delegation
The Bun WebSocket adapter leverages Bun's native HTTP server upgrade capabilities through upgradeWebSocket, which is defined using defineWebSocketHelper. It retrieves the underlying Bun server instance via getBunServer, throwing a TypeError if the server instance is missing from the environment.
export const upgradeWebSocket: UpgradeWebSocket<any> = defineWebSocketHelper((c, events) => {
const server = getBunServer<{
upgrade<T>(
req: Request,
options?: {
data: T
}
): boolean
}>(c)
if (!server) {
throw new TypeError('env has to include the 2nd argument of fetch.')
}Sources: src/adapter/bun/websocket.ts:48-60
Once the server instance is acquired, the adapter calls server.upgrade on the raw request object (c.req.raw), passing custom BunWebSocketData containing registered event listeners, the request URL, and protocol details. If the upgrade succeeds, it returns an empty Response(null) to complete the handshake.
const upgradeResult = server.upgrade<BunWebSocketData>(c.req.raw, {
data: {
events,
url: new URL(c.req.url),
protocol: c.req.url,
},
})
if (upgradeResult) {
return new Response(null)
}
return // failed
})Sources: src/adapter/bun/websocket.ts:61-72
Warning
Calling upgradeWebSocket without supplying the Bun server instance in the second argument of fetch throws a TypeError.
Sources: src/adapter/bun/websocket.ts:58-60
Context Registry and Event Dispatch Bridge
The Bun adapter implements a BunWebSocketHandler interface that bridges native socket lifecycle hooks to Hono's unified WebSocket event handlers. The handler defines three core callback methods (open, close, and message) which interface directly with BunServerWebSocket instances.
| Callback Method | Parameters | Action |
|---|---|---|
open | ws: BunServerWebSocket<T> | Invokes onOpen with an Event and a newly created WSContext. |
close | ws: BunServerWebSocket<T>, code?: number, reason?: string | Dispatches onClose with a CloseEvent containing status codes and reasons. |
message | ws: BunServerWebSocket<T>, message: string | { buffer: ArrayBufferLike } | Normalizes incoming strings or buffer objects and triggers onMessage. |
Sources: src/adapter/bun/websocket.ts:15-19, src/adapter/bun/websocket.ts:74-102
The createWSContext helper builds the runtime WSContext bridge around a BunServerWebSocket<BunWebSocketData>, mapping transmission calls, connection readiness states, URL endpoints, sub-protocols, and closure methods back to the native socket.
export const createWSContext = (ws: BunServerWebSocket<BunWebSocketData>): WSContext => {
return new WSContext({
send: (source, options) => {
ws.send(source, options?.compress)
},
raw: ws,
readyState: ws.readyState,
url: ws.data.url,
protocol: ws.data.protocol,
close(code, reason) {
ws.close(code, reason)
},
})
}Sources: src/adapter/bun/websocket.ts:33-46
Related
On This Page
OverviewWebSocket helper architecture and interfacesCore Contracts and Event TypesFactory Signatures and Upgrade OverloadsNormalized context and event abstractionWSContext Initialization and PropertiesEvent Normalization and Message WrappingCloudflare Workers WebSocket upgrade adapterWebSocketPair Instantiation and Response HandshakingRuntime Testing HarnessBun server WebSocket integrationNative Server Upgrade DelegationContext Registry and Event Dispatch BridgeRelated