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"]