This project demonstrates multi-worker development with Cloudflare Workers, showcasing how to run multiple Workers locally using separate dev commands.
backend/): A Cloudflare Worker with Durable Objects (TestDurableObject) frontend/): A SvelteKit application deployed as a Cloudflare Worker that accesses the backend's Durable Objects via script_name bindingThe frontend references the backend's Durable Object namespace using the script_name configuration, allowing it to access Durable Objects defined in the backend Worker.
Install dependencies and generate Cloudflare types in both folders:
# Backend
cd backend
pnpm install
pnpm cf-typegen
# Frontend
cd frontend
pnpm install
pnpm cf-typegen
Run both Workers in separate terminals:
# Terminal 1 - Backend (runs with wrangler dev)
cd backend
pnpm run dev
# Terminal 2 - Frontend (runs with vite dev)
cd frontend
pnpm run dev
The backend runs using wrangler dev, while the frontend runs using vite dev (via the Cloudflare Vite plugin). This approach uses the "multiple dev commands" pattern, where each Worker runs independently and can communicate via service bindings or Durable Object bindings across Workers.
The backend worker requires NODE_ENV to be set for security. Direct WebSocket access to /ws/connect is only allowed when NODE_ENV is 'development' or 'test'. In production, WebSocket connections must go through the frontend SvelteKit handler which validates authorization.
Set NODE_ENV in backend/wrangler.jsonc:
"vars": {
"NODE_ENV": "development"
}
Or use a .dev.vars file in the backend/ directory:
cd backend
cat > .dev.vars << EOF
NODE_ENV=development
EOF
Note: .dev.vars files are gitignored and only used in local development. For production deployments, set environment variables via wrangler secret put or the Cloudflare dashboard.
The main page includes a WebSocket countdown demo that demonstrates real-time communication using Cloudflare Durable Objects.
MessageCoordinator Durable Object broadcasts to all connected clientsBrowser ←WebSocket→ MessageCoordinator (Durable Object) ←POST→ SvelteKit Action
frontend/src/lib/websocket.ts - WebSocket client with auto-reconnectfrontend/src/lib/components/WebSocketStatus.svelte - Connection status indicator (red/amber/green)backend/src/message-coordinator.ts - Durable Object handling WebSocket connections and broadcastingIn development, Vite's dev server intercepts all WebSocket connections for HMR (Hot Module Replacement), preventing SvelteKit routes from handling WebSocket upgrades.
Solution: The WebSocket client connects directly to the backend worker (port 8787) in dev mode. This is configured via environment variables:
VITE_WS_HOST - WebSocket host (defaults to localhost:8787 in dev, window.location.host in production)VITE_WS_PATH - WebSocket path (defaults to /ws/connect in dev, /api/ws/connect in production)Create a .env.dev file in the frontend/ directory with your development settings:
cd frontend
cat > .env.dev << EOF
VITE_WS_HOST=localhost:8787
VITE_WS_PATH=/ws/connect
EOF
You can override these values by setting environment variables or creating a .env.local file.
Security Note: The backend worker's /ws/connect endpoint only accepts direct connections when NODE_ENV is set to 'development' or 'test'. In production, this endpoint returns 401 Unauthorized, forcing all WebSocket connections to go through the frontend SvelteKit handler at /api/ws/connect which validates authorization.
In production, there's no Vite server - all requests go directly through the Cloudflare Worker, so WebSocket upgrades work normally on the same origin.