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.
index.jsimport { DurableObject } from "cloudflare:workers";
// Worker
export 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 instance,      // 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 Object
export 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,    });  }
}
index.tsimport { DurableObject } from "cloudflare:workers";
export interface Env {  WEBSOCKET_SERVER: DurableObjectNamespace<WebSocketServer>;
}
// Worker
export 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 instance,      // 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 Object
export 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.toml file to include a Durable Object binding and migration based on the namespace and class name chosen previously.
wrangler.tomlname = "websocket-server"
[[durable_objects.bindings]]
name = "WEBSOCKET_SERVER"
class_name = "WebSocketServer"
[[migrations]]
tag = "v1"
new_classes = ["WebSocketServer"]