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("Worker expected Upgrade: websocket", {          status: 426,        });      }
      if (request.method !== "GET") {        return new Response("Worker expected GET method", {          status: 400,        });      }
      // Since we are hard coding the Durable Object ID by providing the constant name 'foo',      // all requests to this Worker will be sent to the same Durable Object instance.      let id = env.WEBSOCKET_SERVER.idFromName("foo");      let stub = env.WEBSOCKET_SERVER.get(id);
      return stub.fetch(request);    }
    return new Response(      `Supported endpoints:/websocket: Expects a WebSocket upgrade request`,      {        status: 200,        headers: {          "Content-Type": "text/plain",        },      },    );  },};
// Durable Objectexport class WebSocketServer extends DurableObject {  // Keeps track of all WebSocket connections  sessions;
  constructor(ctx, env) {    super(ctx, env);    this.sessions = new Map();  }
  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();
    // Generate a random UUID for the session.    const id = crypto.randomUUID();    // Add the WebSocket connection to the map of active sessions.    this.sessions.set(server, { id });
    server.addEventListener("message", (event) => {      this.handleWebSocketMessage(server, event.data);    });
    // If the client closes the connection, the runtime will close the connection too.    server.addEventListener("close", () => {      this.handleConnectionClose(server);    });
    return new Response(null, {      status: 101,      webSocket: client,    });  }
  async handleWebSocketMessage(ws, message) {    const connection = this.sessions.get(ws);
    // Reply back with the same message to the connection    ws.send(      `[Durable Object] message: ${message}, from: ${connection.id}, to: the initiating client. Total connections: ${this.sessions.size}`,    );
    // Broadcast the message to all the connections,    // except the one that sent the message.    this.sessions.forEach((_, session) => {      if (session !== ws) {        session.send(          `[Durable Object] message: ${message}, from: ${connection.id}, to: all clients except the initiating client. Total connections: ${this.sessions.size}`,        );      }    });
    // Broadcast the message to all the connections,    // including the one that sent the message.    this.sessions.forEach((_, session) => {      session.send(        `[Durable Object] message: ${message}, from: ${connection.id}, to: all clients. Total connections: ${this.sessions.size}`,      );    });  }
  async handleConnectionClose(ws) {    this.sessions.delete(ws);    ws.close(1000, "Durable Object is closing WebSocket");  }}import { DurableObject } from 'cloudflare:workers';
// Workerexport default {    async fetch(request: Request, env: Env, ctx: ExecutionContext): 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('Worker expected Upgrade: websocket', {                    status: 426,                });            }
            if (request.method !== 'GET') {                return new Response('Worker expected GET method', {                    status: 400,                });            }
            // Since we are hard coding the Durable Object ID by providing the constant name 'foo',            // all requests to this Worker will be sent to the same Durable Object instance.            let id = env.WEBSOCKET_SERVER.idFromName('foo');            let stub = env.WEBSOCKET_SERVER.get(id);
            return stub.fetch(request);        }
        return new Response(            `Supported endpoints:/websocket: Expects a WebSocket upgrade request`,            {                status: 200,                headers: {                    'Content-Type': 'text/plain',                },            }        );    },};
// Durable Objectexport class WebSocketServer extends DurableObject {    // Keeps track of all WebSocket connections    sessions: Map<WebSocket, { [key: string]: string }>;
    constructor(ctx: DurableObjectState, env: Env) {        super(ctx, env);        this.sessions = new Map();    }
    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();
        // Generate a random UUID for the session.        const id = crypto.randomUUID();        // Add the WebSocket connection to the map of active sessions.        this.sessions.set(server, { id });
        server.addEventListener('message', (event) => {            this.handleWebSocketMessage(server, event.data);        });
        // If the client closes the connection, the runtime will close the connection too.        server.addEventListener('close', () => {            this.handleConnectionClose(server);        });
        return new Response(null, {            status: 101,            webSocket: client,        });    }
    async handleWebSocketMessage(ws: WebSocket, message: string | ArrayBuffer) {        const connection = this.sessions.get(ws)!;
        // Reply back with the same message to the connection        ws.send(`[Durable Object] message: ${message}, from: ${connection.id}, to: the initiating client. Total connections: ${this.sessions.size}`);
        // Broadcast the message to all the connections,        // except the one that sent the message.        this.sessions.forEach((_, session) => {            if (session !== ws) {                session.send(`[Durable Object] message: ${message}, from: ${connection.id}, to: all clients except the initiating client. Total connections: ${this.sessions.size}`);            }        });
        // Broadcast the message to all the connections,        // including the one that sent the message.        this.sessions.forEach((_, session) => {            session.send(`[Durable Object] message: ${message}, from: ${connection.id}, to: all clients. Total connections: ${this.sessions.size}`);        });    }
    async handleConnectionClose(ws: WebSocket) {        this.sessions.delete(ws);        ws.close(1000, 'Durable Object is closing WebSocket');    }}Finally, configure your Wrangler file to include a Durable Object binding and migration based on the namespace and class name chosen previously.
{  "$schema": "./node_modules/wrangler/config-schema.json",  "name": "websocket-server",  "main": "src/index.ts",  "durable_objects": {    "bindings": [      {        "name": "WEBSOCKET_SERVER",        "class_name": "WebSocketServer"      }    ]  },  "migrations": [    {      "tag": "v1",      "new_sqlite_classes": [        "WebSocketServer"      ]    }  ]}name = "websocket-server"main = "src/index.ts"
[[durable_objects.bindings]]name = "WEBSOCKET_SERVER"class_name = "WebSocketServer"
[[migrations]]tag = "v1"new_sqlite_classes = ["WebSocketServer"]Was this helpful?
- Resources
- API
- New to Cloudflare?
- Directory
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- © 2025 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark
-