# How to Implement WebSocket Communication in Deno: Client and Server Guide

> Learn how to implement WebSocket communication in Deno with this guide. Build real-time applications using Deno's native WebSocket API for both clients and servers.

- Repository: [Deno/deno](https://github.com/denoland/deno)
- Tags: how-to-guide
- Published: 2026-02-26

---

**WebSocket communication in Deno is implemented using the native global `WebSocket` class for client connections and `Deno.upgradeWebSocket()` for server-side upgrades, both sharing the same internal prototype defined in [`ext/websocket/01_websocket.js`](https://github.com/denoland/deno/blob/main/ext/websocket/01_websocket.js) and [`ext/http/02_websocket.ts`](https://github.com/denoland/deno/blob/main/ext/http/02_websocket.ts).**

Deno provides a full-featured, browser-compatible WebSocket implementation that requires no external dependencies. According to the denoland/deno source code, the WebSocket stack is split between client-side logic in [`ext/websocket/01_websocket.js`](https://github.com/denoland/deno/blob/main/ext/websocket/01_websocket.js) and server-side upgrade handling in [`ext/http/02_websocket.ts`](https://github.com/denoland/deno/blob/main/ext/http/02_websocket.ts), enabling bidirectional, event-driven communication for real-time applications.

## Understanding Deno's WebSocket Architecture

Deno's WebSocket implementation consists of two primary layers that share a common JavaScript class but handle connection establishment differently based on their role.

### Client-Side WebSocket Class

The global `WebSocket` constructor available in Deno is implemented in [`ext/websocket/01_websocket.js`](https://github.com/denoland/deno/blob/main/ext/websocket/01_websocket.js). This class handles the entire client lifecycle:

- **URL parsing** – The constructor forces schemes to `ws:` or `wss:` (lines 53-95)
- **Permission validation** – Creates a cancel handle via `op_ws_check_permission_and_cancel_handle` (lines 73-78)
- **Resource creation** – Spawns a native handle through `op_ws_create` (lines 81-90)
- **Event polling** – The internal `[_eventLoop]` continuously polls `op_ws_next_event` to dispatch `"message"`, `"close"`, `"error"`, and `"pong"` events (lines 108-180)

The only difference between client and server instances is the **role** (`CLIENT` vs `SERVER`) and how the resource identifier (`rid`) is obtained.

### Server-Side Upgrade Mechanism

For servers, `Deno.upgradeWebSocket()` in [`ext/http/02_websocket.ts`](https://github.com/denoland/deno/blob/main/ext/http/02_websocket.ts) handles the HTTP upgrade handshake:

1. Validates that the `Upgrade` header contains `"websocket"` and the `Connection` header contains `"Upgrade"` (lines 42-58)
2. Generates the `Sec-WebSocket-Accept` header via `op_http_websocket_accept_header` (line 67)
3. Handles optional `sec-websocket-protocol` negotiation (lines 76-89)
4. Creates a server-side socket using `createWebSocketBranded` with role `SERVER` and an idle-timeout value (default 30s) (lines 91-98)
5. Returns a `101 Switching Protocols` `Response` object alongside the socket (lines 69-75)

## Implementing a WebSocket Client

To establish a client connection, instantiate the global `WebSocket` class with a WebSocket URL:

```typescript
// client.ts
const ws = new WebSocket("wss://echo.deno.dev");

// Connection established
ws.addEventListener("open", () => {
  console.log("Connected");
  ws.send("Hello Deno WebSocket!");
});

// Message from server
ws.addEventListener("message", (e) => {
  console.log("Received:", e.data);
  ws.close(1000, "Done");
});

// Close event
ws.addEventListener("close", (e) => {
  console.log(`Closed (code=${e.code}, reason=${e.reason})`);
});

// Error handling
ws.addEventListener("error", (e) => {
  console.error("WebSocket error:", e);
});

```

The constructor in [`ext/websocket/01_websocket.js`](https://github.com/denoland/deno/blob/main/ext/websocket/01_websocket.js) automatically transitions the `readyState` to `OPEN` upon handshake completion and dispatches the `"open"` event. All message framing and queuing is handled internally by the runtime's Rust-backed operations.

## Building a WebSocket Server

Use `Deno.upgradeWebSocket()` within an HTTP handler to upgrade connections:

```typescript
// server.ts
Deno.serve(async (req) => {
  const upgrade = req.headers.get("upgrade") ?? "";
  if (upgrade.toLowerCase() !== "websocket") {
    return new Response("Not a WebSocket request", { status: 400 });
  }

  // Perform the upgrade (calls ext/http/02_websocket.ts)
  const { socket, response } = Deno.upgradeWebSocket(req, {
    idleTimeout: 60, // seconds, default is 30
  });

  // Handle incoming messages
  socket.addEventListener("message", (e) => {
    console.log("Received:", e.data);
    socket.send(`Echo: ${e.data}`);
  });

  socket.addEventListener("close", (e) => {
    console.log(`Connection closed (code=${e.code})`);
  });

  // Return 101 Switching Protocols response
  return response;
});

```

The `upgradeWebSocket` function returns an object containing:
- **`socket`**: A `WebSocket` instance (same prototype as client-side but with role `SERVER`)
- **`response`**: A `Response` object with status 101 containing the required handshake headers

The server's `open` event is emitted once the internal `_wantsUpgrade` promise resolves to a native rid, completing the asynchronous upgrade process (lines 99-124 in [`ext/http/02_websocket.ts`](https://github.com/denoland/deno/blob/main/ext/http/02_websocket.ts)).

## Advanced Configuration Options

### Idle Timeout Management

Server-side WebSockets include automatic idle timeout handling implemented in [`ext/websocket/01_websocket.js`](https://github.com/denoland/deno/blob/main/ext/websocket/01_websocket.js) via `[_serverHandleIdleTimeout]` (lines 154-190). This feature sends periodic ping frames and automatically closes the connection after the specified period of inactivity, preventing resource exhaustion from stale connections.

### Custom HTTP Clients

For advanced networking scenarios, you can pass a custom `HttpClient` to the WebSocket constructor for proxy support or custom TLS configuration:

```typescript
import { HttpClient } from "https://deno.land/std@0.228.0/http/client.ts";

const client = new HttpClient({ proxy: { url: "http://my-proxy:3128" } });
const ws = new WebSocket("ws://example.com/socket", { client });

ws.onopen = () => console.log("Connected via proxy");
ws.onmessage = (e) => console.log("Message:", e.data);

```

The `client` option is validated in [`ext/websocket/01_websocket.js`](https://github.com/denoland/deno/blob/main/ext/websocket/01_websocket.js) (lines 22-42), and the underlying resource ID is passed directly to `op_ws_create`.

## Summary

- **Deno provides native WebSocket support** through the global `WebSocket` class and `Deno.upgradeWebSocket()` without requiring external libraries.
- **Client and server share the same prototype** defined in [`ext/websocket/01_websocket.js`](https://github.com/denoland/deno/blob/main/ext/websocket/01_websocket.js), differing only in their role (`CLIENT` vs `SERVER`) and connection establishment method.
- **Server upgrades require header validation** for `Upgrade: websocket` and `Connection: Upgrade`, plus generation of `Sec-WebSocket-Accept` via `op_http_websocket_accept_header` in [`ext/http/02_websocket.ts`](https://github.com/denoland/deno/blob/main/ext/http/02_websocket.ts).
- **Automatic idle timeout handling** protects server resources by closing inactive connections after a configurable period (default 30 seconds).
- **Type definitions** for the global `WebSocket` and `Deno.upgradeWebSocket` are located in [`cli/tsc/dts/lib.deno_websocket.d.ts`](https://github.com/denoland/deno/blob/main/cli/tsc/dts/lib.deno_websocket.d.ts).

## Frequently Asked Questions

### What is the difference between client and server WebSocket instances in Deno?

Client and server WebSocket instances use the exact same class implementation from [`ext/websocket/01_websocket.js`](https://github.com/denoland/deno/blob/main/ext/websocket/01_websocket.js) and share the same prototype methods (`send`, `close`, `addEventListener`). The only internal differences are the **role** property (`CLIENT` vs `SERVER`) and how the resource identifier (`rid`) is obtained—clients create resources directly via `op_ws_create`, while servers receive their rid through the HTTP upgrade mechanism in [`ext/http/02_websocket.ts`](https://github.com/denoland/deno/blob/main/ext/http/02_websocket.ts).

### How does Deno handle WebSocket ping/pong and idle timeouts?

According to [`ext/websocket/01_websocket.js`](https://github.com/denoland/deno/blob/main/ext/websocket/01_websocket.js), server-side sockets implement `[_serverHandleIdleTimeout]` (lines 154-190) which automatically sends ping frames at intervals and closes the connection with code 1001 if no pong response is received within the configured timeout period (default 30 seconds). This prevents hanging connections from consuming server resources.

### Can I use WebSocket with `Deno.serve()`?

Yes. `Deno.serve()` is fully compatible with `Deno.upgradeWebSocket()`. Pass the incoming `Request` object to `upgradeWebSocket()` within your handler, then return the resulting `response` to complete the 101 Switching Protocols handshake. The socket begins emitting events immediately after the upgrade completes.

### What permissions are required for WebSocket connections in Deno?

Client WebSocket connections require the `--allow-net` permission for the target host and port. According to the source code in [`ext/websocket/01_websocket.js`](https://github.com/denoland/deno/blob/main/ext/websocket/01_websocket.js) (lines 73-78), Deno invokes `op_ws_check_permission_and_cancel_handle` during construction to validate network access before establishing the connection. Server-side WebSockets inherit the permissions from the HTTP server itself.