Build a WebSocket server
Build a WebSocket server using Durable Objects and Workers.
This example shows how to build a WebSocket server using Durable Objects and Workers. The example exposes an endpoint to create a new WebSocket connection. This WebSocket connection echos any message while including the total number of WebSocket connections currently established. For more information, refer to Use Durable Objects with WebSockets.
import { DurableObject } from "cloudflare:workers";
// Workerexport default {  async fetch(request, env, ctx) {    if (request.url.endsWith("/websocket")) {      // Expect to receive a WebSocket Upgrade request.      // If there is one, accept the request and return a WebSocket Response.      const upgradeHeader = request.headers.get('Upgrade');      if (!upgradeHeader || upgradeHeader !== 'websocket') {        return new Response('Durable Object expected Upgrade: websocket', { status: 426 });      }
      // This example will refer to the same Durable Object,      // since the name "foo" is hardcoded.      let id = env.WEBSOCKET_SERVER.idFromName("foo");      let stub = env.WEBSOCKET_SERVER.get(id);
      return stub.fetch(request);    }
    return new Response(null, {      status: 400,      statusText: 'Bad Request',      headers: {        'Content-Type': 'text/plain',      },    });  }};
// Durable Objectexport class WebSocketServer extends DurableObject {  currentlyConnectedWebSockets;
  constructor(ctx, env) {    // This is reset whenever the constructor runs because    // regular WebSockets do not survive Durable Object resets.    //    // WebSockets accepted via the Hibernation API can survive    // a certain type of eviction, but we will not cover that here.    super(ctx, env);    this.currentlyConnectedWebSockets = 0;  }
  async fetch(request) {    // Creates two ends of a WebSocket connection.    const webSocketPair = new WebSocketPair();    const [client, server] = Object.values(webSocketPair);
    // Calling `accept()` tells the runtime that this WebSocket is to begin terminating    // request within the Durable Object. It has the effect of "accepting" the connection,    // and allowing the WebSocket to send and receive messages.    server.accept();    this.currentlyConnectedWebSockets += 1;
    // Upon receiving a message from the client, the server replies with the same message,    // and the total number of connections with the "[Durable Object]: " prefix    server.addEventListener('message', (event) => {      server.send(`[Durable Object] currentlyConnectedWebSockets: ${this.currentlyConnectedWebSockets}`);    });
    // If the client closes the connection, the runtime will close the connection too.    server.addEventListener('close', (cls) => {      this.currentlyConnectedWebSockets -= 1;      server.close(cls.code, "Durable Object is closing WebSocket");    });
    return new Response(null, {      status: 101,      webSocket: client,    });  }}import { DurableObject } from "cloudflare:workers";
export interface Env {  WEBSOCKET_SERVER: DurableObjectNamespace<WebSocketServer>;}
// Workerexport default {  async fetch(request, env, ctx): Promise<Response> {    if (request.url.endsWith("/websocket")) {      // Expect to receive a WebSocket Upgrade request.      // If there is one, accept the request and return a WebSocket Response.      const upgradeHeader = request.headers.get('Upgrade');      if (!upgradeHeader || upgradeHeader !== 'websocket') {        return new Response('Durable Object expected Upgrade: websocket', { status: 426 });      }
      // This example will refer to the same Durable Object,      // since the name "foo" is hardcoded.      let id = env.WEBSOCKET_SERVER.idFromName("foo");      let stub = env.WEBSOCKET_SERVER.get(id);
      return stub.fetch(request);    }
    return new Response(null, {      status: 400,      statusText: 'Bad Request',      headers: {        'Content-Type': 'text/plain',      },    });  }} satisfies ExportedHandler<Env>;
// Durable Objectexport class WebSocketServer extends DurableObject {  currentlyConnectedWebSockets: number;
  constructor(ctx: DurableObjectState, env: Env) {    // This is reset whenever the constructor runs because    // regular WebSockets do not survive Durable Object resets.    //    // WebSockets accepted via the Hibernation API can survive    // a certain type of eviction, but we will not cover that here.    super(ctx, env);    this.currentlyConnectedWebSockets = 0;  }
  async fetch(request: Request): Promise<Response> {    // Creates two ends of a WebSocket connection.    const webSocketPair = new WebSocketPair();    const [client, server] = Object.values(webSocketPair);
    // Calling `accept()` tells the runtime that this WebSocket is to begin terminating    // request within the Durable Object. It has the effect of "accepting" the connection,    // and allowing the WebSocket to send and receive messages.    server.accept();    this.currentlyConnectedWebSockets += 1;
    // Upon receiving a message from the client, the server replies with the same message,    // and the total number of connections with the "[Durable Object]: " prefix    server.addEventListener('message', (event: MessageEvent) => {      server.send(`[Durable Object] currentlyConnectedWebSockets: ${this.currentlyConnectedWebSockets}`);    });
    // If the client closes the connection, the runtime will close the connection too.    server.addEventListener('close', (cls: CloseEvent) => {      this.currentlyConnectedWebSockets -= 1;      server.close(cls.code, "Durable Object is closing WebSocket");    });
    return new Response(null, {      status: 101,      webSocket: client,    });  }}Finally, configure your Wrangler file to include a Durable Object binding and migration based on the namespace and class name chosen previously.
{  "name": "websocket-server",  "durable_objects": {    "bindings": [      {        "name": "WEBSOCKET_SERVER",        "class_name": "WebSocketServer"      }    ]  },  "migrations": [    {      "tag": "v1",      "new_classes": [        "WebSocketServer"      ]    }  ]}name = "websocket-server"
[[durable_objects.bindings]]name = "WEBSOCKET_SERVER"class_name = "WebSocketServer"
[[migrations]]tag = "v1"new_classes = ["WebSocketServer"]