# How the Cloudflare OS Workshop Frontend Communicates with the Backend

> Discover how the Cloudflare OS workshop frontend uses Cap'n Web RPC and WebSockets for type-safe communication with the backend via a shared TypeScript API.

- Repository: [Cloudflare/cloudflare-os](https://github.com/cloudflare/cloudflare-os)
- Tags: internals
- Published: 2026-09-04

---

**The Cloudflare OS workshop frontend communicates with its backend through Cap'n Web RPC over a persistent WebSocket connection, using a shared TypeScript API definition for type-safe remote procedure calls.**

The `cloudflare/cloudflare-os` repository implements a full-stack workshop application demonstrating edge computing patterns. The frontend is a single-page React application that maintains a persistent, bidirectional communication channel with the backend worker using Cap'n Proto serialization over WebSockets.

## Shared API Contract in [`packages/workshop-shared/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/api.ts)

All communication is governed by the `WorkshopApi` interface defined in [`packages/workshop-shared/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/api.ts). This file serves as the single source of truth for both frontend and backend, ensuring that method signatures and data shapes remain synchronized across the stack.

```ts
// packages/workshop-shared/src/api.ts
export interface WorkshopApi {
  /** Returns the list of available gadgets */
  listGadgets(): Promise<string[]>;

  /** Creates a new workspace */
  createWorkspace(name: string): Promise<{ id: string }>;

  /** Sends a chat message to the agent */
  sendMessage(workspaceId: string, text: string): Promise<void>;
}

```

Any changes to the RPC surface must update this shared definition, providing compile-time guarantees that the frontend and backend agree on the protocol.

## WebSocket Transport Configuration ([`packages/workshop-frontend/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-frontend/src/api.ts))

The frontend establishes a binary WebSocket connection to the `/api/ws` endpoint immediately on application startup. The connection remains open for the lifetime of the page session, eliminating handshake overhead for subsequent RPC calls.

```tsx
// packages/workshop-frontend/src/api.ts
import { createRpcClient } from '@gadgets/capnweb';
import type { WorkshopApi } from '@gadgets/workshop-shared/api';

const socket = new WebSocket(`${location.origin}/api/ws`);
socket.binaryType = 'arraybuffer';

export const workshopApi: WorkshopApi = createRpcClient<WorkshopApi>(socket);

```

Setting `binaryType` to `'arraybuffer'` ensures efficient transmission of Cap'n Proto's binary serialization format without additional encoding overhead.

## RPC Client Initialization with Cap'n Web

The `createRpcClient` function from the internal `capnweb` library constructs a typed client stub from the shared `WorkshopApi` interface. This stub exposes remote methods as standard async functions while handling message framing, pipelining, and deserialization internally.

Calls made through the stub are automatically serialized into Cap'n Web messages and transmitted via the underlying WebSocket. The backend implementation in [`packages/workshop-backend/src/rpc.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/rpc.ts) exposes corresponding handlers that satisfy the same `WorkshopApi` interface, ensuring end-to-end type safety.

## React Integration and State Management

RPC stubs are not plain JavaScript values and cannot be stored directly in React state without triggering setter function conflicts. The frontend wraps stubs in plain objects to safely persist them within component state.

```tsx
// packages/workshop-frontend/src/App.tsx
import { useEffect, useState } from 'react';
import { workshopApi } from './api';

export function GadgetList() {
  const [gadgets, setGadgets] = useState<string[]>([]);
  const [apiStub] = useState({ stub: workshopApi });

  useEffect(() => {
    apiStub.stub.listGadgets().then(setGadgets);
  }, [apiStub]);

  return (
    <ul>
      {gadgets.map((g) => (
        <li key={g}>{g}</li>
      ))}
    </ul>
  );
}

```

This wrapping pattern prevents React from mistaking the stub for a state-updating function while preserving reference stability across renders.

## Resource Cleanup and Connection Lifecycle

When components unmount or explicit cleanup is required, the frontend disposes of RPC stubs to release backend resources. Stubs implement the explicit resource management protocol using `Symbol.dispose`.

```tsx
// Cleanup pattern in React components
useEffect(() => {
  const stub = workshopApi.createWorkspace("temp");

  return () => {
    // Explicit disposal prevents resource leaks on the backend
    stub[Symbol.dispose]?.();
  };
}, []);

```

Alternatively, TypeScript's `using` syntax can manage automatic cleanup when scopes exit, ensuring that transient RPC objects do not accumulate on the server.

## Summary

- **Protocol**: The frontend uses Cap'n Web RPC over a persistent WebSocket to `/api/ws` for all backend communication.
- **Type Safety**: The shared `WorkshopApi` interface in [`packages/workshop-shared/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/api.ts) guarantees compile-time synchronization between client and server.
- **Transport**: Binary WebSocket connections with `arraybuffer` type handle serialized Cap'n Proto messages efficiently.
- **React Patterns**: RPC stubs are wrapped in plain objects (`{ stub }`) before storage in React state to avoid setter conflicts.
- **Resource Management**: Explicit disposal via `Symbol.dispose` or `using` blocks prevents backend resource leaks when components unmount.

## Frequently Asked Questions

### What protocol does the workshop frontend use to communicate with the backend?

The frontend uses **Cap'n Web RPC**, a protocol based on Cap'n Proto serialization that runs over a persistent WebSocket connection. This provides type-safe remote procedure calls with lower overhead than HTTP-based REST or GraphQL, as implemented in the `cloudflare/cloudflare-os` repository.

### How is the WebSocket connection configured in the workshop frontend?

In [`packages/workshop-frontend/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-frontend/src/api.ts), the frontend creates a `WebSocket` targeting `/api/ws` and sets `socket.binaryType = 'arraybuffer'`. This binary mode is critical for transmitting Cap'n Proto's compact binary format without Base64 encoding overhead.

### Why are RPC stubs wrapped in plain objects when stored in React state?

RPC stubs are callable objects that React's `useState` hook may misinterpret as state-updating functions. Wrapping them in plain objects like `{ stub }` prevents the setter from treating the stub as a functional update, ensuring predictable state behavior while preserving the typed client reference.

### How does the frontend handle cleanup of RPC connections?

Components dispose of stubs explicitly using `stub[Symbol.dispose]()` in `useEffect` cleanup functions or by leveraging TypeScript's `using` keyword for automatic scope-based cleanup. This signals the backend to release associated resources, preventing memory leaks in long-running sessions.