> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/honojs/hono/llms.txt
> Use this file to discover all available pages before exploring further.

# WebSocket

> Real-time bidirectional communication with WebSocket helper

The WebSocket helper provides a runtime-agnostic way to handle WebSocket connections in Hono applications.

## Overview

Hono's WebSocket helper is a wrapper that works across different runtimes (Cloudflare Workers, Deno, Bun, etc.) by abstracting WebSocket implementation details.

<Note>
  The WebSocket helper itself doesn't implement WebSockets. You need to use runtime-specific adaptors that implement the `upgradeWebSocket` function.
</Note>

## Adaptors

Use runtime-specific adaptors:

* **Cloudflare Workers**: `hono/cloudflare-workers`
* **Deno**: `hono/deno`
* **Bun**: `hono/bun`
* **Node.js**: Third-party adaptor needed

## Import

```typescript theme={null}
import { upgradeWebSocket } from 'hono/cloudflare-workers'
// or 'hono/deno' or 'hono/bun' depending on runtime
```

## Basic Usage

### Simple WebSocket Server

```typescript theme={null}
import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/cloudflare-workers'

const app = new Hono()

app.get(
  '/ws',
  upgradeWebSocket((c) => {
    return {
      onMessage(event, ws) {
        console.log(`Message from client: ${event.data}`)
        ws.send('Hello from server!')
      },
      onClose() {
        console.log('Connection closed')
      },
    }
  })
)

export default app
```

### Client-Side Connection

```javascript theme={null}
const ws = new WebSocket('ws://localhost:8787/ws')

ws.onopen = () => {
  console.log('Connected')
  ws.send('Hello Server!')
}

ws.onmessage = (event) => {
  console.log('Message from server:', event.data)
}

ws.onclose = () => {
  console.log('Disconnected')
}

ws.onerror = (error) => {
  console.error('WebSocket error:', error)
}
```

## Event Handlers

The WebSocket helper provides four event handlers:

### onOpen

Called when the connection is established:

```typescript theme={null}
app.get(
  '/ws',
  upgradeWebSocket((c) => {
    return {
      onOpen(event, ws) {
        console.log('Client connected')
        console.log('Connection URL:', ws.url?.href)
        console.log('Protocol:', ws.protocol)
      },
    }
  })
)
```

### onMessage

Called when a message is received:

```typescript theme={null}
app.get(
  '/ws',
  upgradeWebSocket((c) => {
    return {
      onMessage(event, ws) {
        const message = event.data
        
        if (typeof message === 'string') {
          ws.send(`Echo: ${message}`)
        } else if (message instanceof Blob) {
          console.log('Received blob')
        } else if (message instanceof ArrayBuffer) {
          console.log('Received binary data')
        }
      },
    }
  })
)
```

### onClose

Called when the connection closes:

```typescript theme={null}
app.get(
  '/ws',
  upgradeWebSocket((c) => {
    return {
      onClose(event, ws) {
        console.log('Connection closed')
        console.log('Code:', event.code)
        console.log('Reason:', event.reason)
      },
    }
  })
)
```

### onError

Called when an error occurs:

```typescript theme={null}
app.get(
  '/ws',
  upgradeWebSocket((c) => {
    return {
      onError(event, ws) {
        console.error('WebSocket error occurred')
      },
    }
  })
)
```

## WSContext

The `ws` parameter in event handlers is a `WSContext` instance:

### send()

Send data to the client:

```typescript theme={null}
onMessage(event, ws) {
  // Send string
  ws.send('Hello')
  
  // Send binary data
  ws.send(new Uint8Array([1, 2, 3]))
  
  // Send with options
  ws.send('compressed message', { compress: true })
}
```

### close()

Close the connection:

```typescript theme={null}
onMessage(event, ws) {
  if (event.data === 'bye') {
    ws.close(1000, 'Goodbye')
  }
}
```

<ParamField path="code" type="number" default="1000">
  Close code (1000 = normal closure)
</ParamField>

<ParamField path="reason" type="string">
  Human-readable close reason
</ParamField>

### readyState

Get the connection state:

```typescript theme={null}
onMessage(event, ws) {
  console.log('Ready state:', ws.readyState)
  // 0 = CONNECTING
  // 1 = OPEN
  // 2 = CLOSING
  // 3 = CLOSED
  
  if (ws.readyState === 1) {
    ws.send('Connection is open')
  }
}
```

### url

Access the connection URL:

```typescript theme={null}
onOpen(event, ws) {
  if (ws.url) {
    console.log('Full URL:', ws.url.href)
    console.log('Path:', ws.url.pathname)
    console.log('Query:', ws.url.searchParams)
  }
}
```

### protocol

Get the negotiated subprotocol:

```typescript theme={null}
onOpen(event, ws) {
  console.log('Protocol:', ws.protocol)
}
```

### raw

Access the underlying WebSocket:

```typescript theme={null}
onOpen(event, ws) {
  // Runtime-specific WebSocket instance
  console.log('Raw WebSocket:', ws.raw)
}
```

## Advanced Usage

### Chat Server Example

```typescript theme={null}
import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/cloudflare-workers'

const app = new Hono()

// Store active connections
const connections = new Set<WSContext>()

app.get(
  '/chat',
  upgradeWebSocket((c) => {
    let username = 'Anonymous'
    
    return {
      onOpen(event, ws) {
        connections.add(ws)
        console.log(`User connected. Total: ${connections.size}`)
      },
      
      onMessage(event, ws) {
        const message = event.data
        
        if (typeof message === 'string') {
          // Parse message
          try {
            const data = JSON.parse(message)
            
            if (data.type === 'setName') {
              username = data.name
            } else if (data.type === 'message') {
              // Broadcast to all clients
              const broadcast = JSON.stringify({
                username,
                message: data.message,
                timestamp: Date.now(),
              })
              
              for (const conn of connections) {
                if (conn.readyState === 1) {
                  conn.send(broadcast)
                }
              }
            }
          } catch (e) {
            ws.send(JSON.stringify({ error: 'Invalid message format' }))
          }
        }
      },
      
      onClose(event, ws) {
        connections.delete(ws)
        console.log(`User disconnected. Total: ${connections.size}`)
      },
      
      onError(event, ws) {
        console.error('WebSocket error')
        connections.delete(ws)
      },
    }
  })
)
```

### Using Context Data

Access Hono context inside WebSocket handlers:

```typescript theme={null}
app.get(
  '/ws',
  upgradeWebSocket((c) => {
    const userId = c.req.query('userId')
    const token = c.req.header('Authorization')
    
    return {
      onOpen(event, ws) {
        console.log(`User ${userId} connected with token ${token}`)
      },
      
      onMessage(event, ws) {
        // userId and token are accessible via closure
        ws.send(`Hello user ${userId}`)
      },
    }
  })
)
```

### Binary Data

Handle binary messages:

```typescript theme={null}
app.get(
  '/binary',
  upgradeWebSocket((c) => {
    return {
      onMessage(event, ws) {
        const data = event.data
        
        if (data instanceof ArrayBuffer) {
          const view = new Uint8Array(data)
          console.log('Received bytes:', view.length)
          
          // Echo back
          ws.send(data)
        }
      },
    }
  })
)
```

## Direct Upgrade

Upgrade in a handler directly:

```typescript theme={null}
import { upgradeWebSocket } from 'hono/cloudflare-workers'

app.get('/ws', async (c) => {
  // Check authentication
  const token = c.req.header('Authorization')
  if (!token) {
    return c.text('Unauthorized', 401)
  }
  
  // Upgrade to WebSocket
  return upgradeWebSocket(c, {
    onMessage(event, ws) {
      ws.send('Authenticated message')
    },
  })
})
```

## Testing WebSockets

Test WebSocket endpoints:

```typescript theme={null}
import { testClient } from 'hono/testing'

const client = testClient(app)

// Note: WebSocket testing requires special handling
// Use runtime-specific testing tools
```

## Creating Custom Adaptors

Create a custom WebSocket adaptor:

```typescript theme={null}
import { defineWebSocketHelper } from 'hono/websocket'
import type { WSEvents } from 'hono/websocket'

export const upgradeWebSocket = defineWebSocketHelper(
  (c, events, options) => {
    // Implement runtime-specific WebSocket upgrade logic
    // Return Response or void
  }
)
```

## Close Codes

Standard WebSocket close codes:

<ParamField path="1000" type="Normal Closure">
  Successful operation / regular socket shutdown
</ParamField>

<ParamField path="1001" type="Going Away">
  Browser tab closed or server is shutting down
</ParamField>

<ParamField path="1002" type="Protocol Error">
  Endpoint received a malformed frame
</ParamField>

<ParamField path="1003" type="Unsupported Data">
  Endpoint received data it cannot accept
</ParamField>

<ParamField path="1006" type="Abnormal Closure">
  No close frame received (connection lost)
</ParamField>

<ParamField path="1007" type="Invalid Data">
  Endpoint received inconsistent message
</ParamField>

<ParamField path="1008" type="Policy Violation">
  Endpoint received message violating policy
</ParamField>

<ParamField path="1009" type="Message Too Big">
  Message is too big to process
</ParamField>

<ParamField path="1011" type="Internal Error">
  Server encountered an unexpected condition
</ParamField>

## Use Cases

<CardGroup cols={2}>
  <Card title="Real-time Chat" icon="comments">
    Build chat applications with instant message delivery
  </Card>

  <Card title="Live Updates" icon="arrows-rotate">
    Push live data updates to connected clients
  </Card>

  <Card title="Gaming" icon="gamepad">
    Real-time multiplayer game state synchronization
  </Card>

  <Card title="Collaboration" icon="users">
    Collaborative editing and presence features
  </Card>
</CardGroup>

## Best Practices

<Note>
  Always handle connection cleanup in `onClose` and `onError` to prevent memory leaks.
</Note>

<Warning>
  WebSocket connections are stateful and consume resources. Implement connection limits and timeouts for production.
</Warning>

<Info>
  For one-way server-to-client communication, consider using Server-Sent Events (SSE) instead, which is simpler and works over HTTP.
</Info>

## Comparison with SSE

| Feature         | WebSocket          | SSE                   |
| --------------- | ------------------ | --------------------- |
| Direction       | Bidirectional      | Server to client only |
| Protocol        | WebSocket (ws\://) | HTTP                  |
| Browser Support | Modern browsers    | All modern browsers   |
| Complexity      | Higher             | Lower                 |
| Auto Reconnect  | Manual             | Automatic             |
| Binary Support  | Yes                | No                    |

Choose WebSocket when you need bidirectional communication. Use SSE for simpler server-to-client streaming.
