# How code-server Handles WebSocket Connections for Real-Time Editor Communication

> Discover how code-server manages WebSocket connections for real-time editor sync. Learn about its routing, TLS proxying, and VS Code integration.

- Repository: [Coder/code-server](https://github.com/coder/code-server)
- Tags: internals
- Published: 2026-03-01

---

**code-server upgrades standard HTTP connections to WebSockets through an Express-compatible routing layer in [`src/node/wsRouter.ts`](https://github.com/coder/code-server/blob/main/src/node/wsRouter.ts), optionally proxies TLS sockets via `SocketProxyProvider` in [`src/node/socket.ts`](https://github.com/coder/code-server/blob/main/src/node/socket.ts), and forwards them to the embedded VS Code server to enable bidirectional synchronization of editor state, terminals, and diagnostics.**

The `coder/code-server` project runs the VS Code web interface inside a Node.js HTTP server, requiring robust **WebSocket connections for real-time editor communication** to stream cursor positions, file changes, and terminal output between the browser and backend. This architecture leverages a custom router abstraction that bridges Node’s native `upgrade` events with Express middleware patterns.

## Bootstrapping the HTTP and WebSocket Server

The server initialization begins in [`src/node/app.ts`](https://github.com/coder/code-server/blob/main/src/node/app.ts), where the `createApp` function constructs an Express HTTP server and a parallel `wsRouter` instance to handle protocol upgrades.

When the underlying Node HTTP server emits an `upgrade` event, the `handleUpgrade` utility (defined in [`src/node/wsRouter.ts`](https://github.com/coder/code-server/blob/main/src/node/wsRouter.ts)) intercepts the raw socket, pauses it to prevent data loss, and forwards the request into the Express pipeline. This allows WebSocket handshakes to utilize standard Express routing and middleware.

```typescript
const wsRouter = express()
handleUpgrade(wsRouter, server)  // src/node/app.ts#L87-L89

```

The `handleUpgrade` function essentially treats WebSocket upgrades as HTTP GET requests, enabling the use of familiar Express patterns like route matching and authentication middleware before the socket is fully established.

## The WebSocket Router Abstraction

At the core of the system lies the `WebsocketRouter` class in [`src/node/wsRouter.ts`](https://github.com/coder/code-server/blob/main/src/node/wsRouter.ts). This wrapper exposes a `ws()` method that registers routes using the standard `router.get()` API but receives a specialized `WebsocketRequest` object containing the paused socket (`ws`) and the initial `head` buffer.

When a handler finishes processing the upgrade, it sets `wreq._ws_handled = true` to indicate successful handling; otherwise, the router returns a 404 response.

```typescript
export class WebsocketRouter {
  public ws(route, ...handlers) {
    this.router.get(route, ...handlers.map(handler => (req, res, next) => {
      (req as InternalWebsocketRequest)._ws_handled = true
      return handler(req as WebsocketRequest, res, next)
    }))
  }
}  // src/node/wsRouter.ts#L40-L62

```

This abstraction ensures that **WebSocket routing** integrates seamlessly with existing Express middleware stacks, including authentication and origin validation.

## Health Check Endpoint Implementation

The [`src/node/routes/health.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/health.ts) file demonstrates a minimal WebSocket implementation. It registers a route at `/health` that upgrades the connection and echoes a JSON health status whenever the client transmits a message.

The handler uses the `ws` library’s `handleUpgrade` method to promote the paused socket to a full `WebSocket` instance, then immediately calls `req.ws.resume()` to allow data flow.

```typescript
wsRouter.ws("/", async req => {
  wss.handleUpgrade(req, req.ws, req.head, ws => {
    ws.addEventListener("message", () => ws.send(JSON.stringify({event:"health", …})))
    req.ws.resume()
  })
})  // src/node/routes/health.ts#L15-L28

```

This pattern serves as a lightweight example for custom real-time endpoints within the code-server ecosystem.

## Proxying TLS Sockets to the VS Code Server

The primary **editor WebSocket** route resides in [`src/node/routes/vscode.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/vscode.ts). A catch-all regex route (`wsRouter.ws(/.*/, …)`) intercepts all WebSocket traffic destined for the editor, applies security middleware (`ensureOrigin`, `ensureAuthenticated`, `ensureVSCodeLoaded`), and prepares the socket for the VS Code server.

Because TLS-wrapped sockets cannot be directly passed to child processes, the route utilizes `SocketProxyProvider` from [`src/node/socket.ts`](https://github.com/coder/code-server/blob/main/src/node/socket.ts) to strip encryption. The provider creates a short-lived Unix domain socket, generates a UUID for handshake validation, and pipes the original TLS stream to a plain `net.Socket` that the VS Code server can consume.

```typescript
wsRouter.ws(/.*/, ensureOrigin, ensureAuthenticated, ensureVSCodeLoaded,
  async (req: WebsocketRequest) => {
    const wrappedSocket = await socketProxyProvider.createProxy(req.ws)
    vscodeServer!.handleUpgrade(req, wrappedSocket as net.Socket)
    req.ws.resume()
})  // src/node/routes/vscode.ts#L45-L53

```

Once upgraded, the connection facilitates the full VS Code protocol, including language server communication, file system watchers, and integrated terminal I/O.

## TLS Socket Proxying Mechanism

The `SocketProxyProvider` class in [`src/node/socket.ts`](https://github.com/coder/code-server/blob/main/src/node/socket.ts) solves the architectural constraint that encrypted sockets cannot be file-descriptor-mapped to spawned processes. When `createProxy(req.ws)` is invoked, the provider:

1. Connects to a proxy pipe (Unix socket)
2. Transmits a unique UUID to the proxy service
3. Pipes data between the original TLS socket and the proxy connection once the UUID is verified

```typescript
const proxy = net.connect(this.proxyPipe)
proxy.once("connect", () => proxy.write(id))
// …later, when the proxy receives the same id, it pipes data between sockets.
// src/node/socket.ts#L35-L71

```

This indirection preserves end-to-end encryption between the browser and code-server while presenting a plain TCP socket to the internal VS Code server, maintaining compatibility with the upstream VS Code architecture.

## Summary

- code-server handles **WebSocket upgrades** in [`src/node/app.ts`](https://github.com/coder/code-server/blob/main/src/node/app.ts) by routing Node `upgrade` events through an Express-compatible layer defined in [`src/node/wsRouter.ts`](https://github.com/coder/code-server/blob/main/src/node/wsRouter.ts).
- The `WebsocketRouter` class wraps Express routing to provide a `ws()` method that receives paused sockets and upgrade headers as `WebsocketRequest` objects.
- The health check endpoint in [`src/node/routes/health.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/health.ts) illustrates a minimal echo pattern using `wss.handleUpgrade()` and `req.ws.resume()`.
- Editor traffic in [`src/node/routes/vscode.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/vscode.ts) utilizes `SocketProxyProvider` from [`src/node/socket.ts`](https://github.com/coder/code-server/blob/main/src/node/socket.ts) to convert TLS sockets into plain TCP streams before forwarding to the VS Code server’s `handleUpgrade` method.
- This architecture enables real-time synchronization while maintaining TLS security and Express middleware compatibility.

## Frequently Asked Questions

### How does code-server route WebSocket upgrades through Express?

code-server uses the `handleUpgrade` function in [`src/node/wsRouter.ts`](https://github.com/coder/code-server/blob/main/src/node/wsRouter.ts) to listen for Node’s native `upgrade` event on the HTTP server. It pauses the incoming socket and forwards the request into the Express router as a standard GET request, allowing middleware like authentication to execute before the WebSocket handshake completes.

### Why does code-server proxy TLS sockets before forwarding to VS Code?

TLS sockets cannot be passed via file descriptors to child processes in Node.js. The `SocketProxyProvider` in [`src/node/socket.ts`](https://github.com/coder/code-server/blob/main/src/node/socket.ts) creates a Unix domain socket intermediary that strips the TLS layer, generating a plain `net.Socket` that the VS Code server can handle while maintaining encrypted communication between the client and the code-server host.

### What is the role of the `_ws_handled` flag in the WebSocket router?

The `_ws_handled` property on the `InternalWebsocketRequest` object signals whether a registered route handler successfully claimed the WebSocket upgrade. If no handler sets this flag, the `WebsocketRouter` automatically returns a 404 response, preventing orphaned connections and providing clear error semantics for unmatched WebSocket paths.

### How does the health check endpoint validate WebSocket functionality?

The `/health` endpoint in [`src/node/routes/health.ts`](https://github.com/coder/code-server/blob/main/src/node/routes/health.ts) upgrades incoming connections using the `ws` library’s `handleUpgrade` method, then listens for client messages and responds with JSON payloads containing server health metrics. This validates that the full upgrade pipeline—from HTTP handshake through socket resumption—is operational.