How to Implement WebSocket Communication in Deno: Client and Server Guide
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 and 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 and server-side upgrade handling in 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. This class handles the entire client lifecycle:
- URL parsing – The constructor forces schemes to
ws:orwss:(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 pollsop_ws_next_eventto 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 handles the HTTP upgrade handshake:
- Validates that the
Upgradeheader contains"websocket"and theConnectionheader contains"Upgrade"(lines 42-58) - Generates the
Sec-WebSocket-Acceptheader viaop_http_websocket_accept_header(line 67) - Handles optional
sec-websocket-protocolnegotiation (lines 76-89) - Creates a server-side socket using
createWebSocketBrandedwith roleSERVERand an idle-timeout value (default 30s) (lines 91-98) - Returns a
101 Switching ProtocolsResponseobject alongside the socket (lines 69-75)
Implementing a WebSocket Client
To establish a client connection, instantiate the global WebSocket class with a WebSocket URL:
// 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 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:
// 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: AWebSocketinstance (same prototype as client-side but with roleSERVER)response: AResponseobject 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).
Advanced Configuration Options
Idle Timeout Management
Server-side WebSockets include automatic idle timeout handling implemented in 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:
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 (lines 22-42), and the underlying resource ID is passed directly to op_ws_create.
Summary
- Deno provides native WebSocket support through the global
WebSocketclass andDeno.upgradeWebSocket()without requiring external libraries. - Client and server share the same prototype defined in
ext/websocket/01_websocket.js, differing only in their role (CLIENTvsSERVER) and connection establishment method. - Server upgrades require header validation for
Upgrade: websocketandConnection: Upgrade, plus generation ofSec-WebSocket-Acceptviaop_http_websocket_accept_headerinext/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
WebSocketandDeno.upgradeWebSocketare located incli/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 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.
How does Deno handle WebSocket ping/pong and idle timeouts?
According to 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 (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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →