Real-time global chatroom. Firebase auth (email + Google). Cloudflare Workers + D1 + Durable Objects.
Browser
│ Firebase Auth (ID token)
│
├──► Cloudflare Pages (static frontend)
│
└──► Cloudflare Worker (REST API + WS upgrade)
├── D1 Database (message storage)
└── Durable Object (WebSocket hub — real-time broadcast)
Real-time flow:
- User opens page → Firebase auth → gets ID token
- Frontend opens
wss://worker/api/ws?token=... - Worker verifies token, upgrades to the
ChatRoomDurable Object - Durable Object holds all live WebSocket connections in memory
- When any user POSTs a message → Worker saves to D1 → calls
DO /broadcast→ DO fans out to every connected socket instantly
voidchat/
├── schema.sql ← D1 schema (run once)
│
├── frontend/
│ ├── index.html ← Full frontend (Firebase auth + chat UI)
│ ├── manifest.json ← PWA manifest
│ ├── _headers ← Cloudflare Pages security headers
│ ├── icon-192.png ← PWA icon (add your own)
│ └── icon-512.png ← PWA icon (add your own)
│
└── worker/
├── wrangler.toml ← Worker config (D1 + DO bindings)
└── src/
├── index.js ← Router + WS upgrade entry point
├── chatroom.js ← ChatRoom Durable Object
├── firebase-verify.js ← Firebase JWT verification (no npm needed)
├── utils.js ← Auth helpers, CORS, response builders
└── routes/
├── messages.js ← GET/POST /api/messages
├── online.js ← GET/POST /api/online
├── announce.js ← POST /api/announce
└── admin/
├── clear.js ← POST /api/admin/clear
├── ban.js ← POST /api/admin/ban
├── unban.js ← POST /api/admin/unban
├── users.js ← GET /api/admin/users
└── delete-message.js ← POST /api/admin/delete-message
- Cloudflare account (free tier works)
- Wrangler CLI —
npm install -g wrangler - Firebase project
- Go to Firebase Console → Add project
- In your project → Authentication → Get started
- Enable Email/Password provider
- Enable Google provider
- Go to Project Settings (gear icon) → General → scroll to Your apps → Add app → Web
- Register the app, copy the
firebaseConfigobject — you'll need it shortly - Note your Project ID (shown in Project Settings)
# Login to Cloudflare
wrangler login
# Create the database
wrangler d1 create voidchat-db
# Copy the database_id from the output
# Apply the schema
wrangler d1 execute voidchat-db --file=schema.sqlOpen worker/wrangler.toml and replace YOUR_D1_DATABASE_ID with the ID you just got.
cd worker
# Set secrets (you'll be prompted to paste values)
wrangler secret put FIREBASE_PROJECT_ID
# → paste your Firebase project ID (e.g. my-project-abc123)
wrangler secret put ADMIN_UID
# → paste the Firebase UID of the user you want as admin
# (sign in once on the frontend, then find the UID in Firebase Console → Authentication → Users)
wrangler secret put FRONTEND_ORIGIN
# → paste your Cloudflare Pages URL (e.g. https://voidchat.pages.dev)
# (use * during development if you don't know it yet)
# Deploy
wrangler deploy
# Note the Worker URL: https://voidchat-api.<subdomain>.workers.devOpen frontend/index.html and find the config section near the bottom:
const FIREBASE_CONFIG = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_PROJECT.firebaseapp.com",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_PROJECT.appspot.com",
messagingSenderId: "YOUR_SENDER_ID",
appId: "YOUR_APP_ID",
};
const API_BASE = 'https://voidchat-api.YOUR_SUBDOMAIN.workers.dev';Replace all YOUR_* values with:
- FIREBASE_CONFIG values → from Firebase Console → Project Settings → Your apps → Web app → SDK snippet
- API_BASE → your Worker URL from Step 3
Option A — GitHub (recommended)
- Push the
frontend/folder contents to a GitHub repository - Go to Cloudflare Dashboard → Pages → Create a project
- Connect GitHub → select your repo
- Build settings:
- Build command: (leave empty)
- Output directory:
/(or wherever index.html is)
- Deploy → note your Pages URL (e.g.
https://voidchat.pages.dev)
Option B — Direct upload
cd frontend
wrangler pages deploy . --project-name=voidchat- Go back to the Worker and update
FRONTEND_ORIGINsecret with the actual Pages URL:
cd ../worker
wrangler secret put FRONTEND_ORIGIN
# → https://voidchat.pages.dev- Firebase Console → Authentication → Settings → Authorized domains
- Add your Pages domain (e.g.
voidchat.pages.dev)
- Open your deployed site and sign in with the account you want to be admin
- Go to Firebase Console → Authentication → Users
- Find your user → copy the UID
- Update the Worker secret:
cd worker
wrangler secret put ADMIN_UID
# → paste the UID
wrangler deployThe admin tab in the sidebar will appear automatically for that account.
All endpoints require Authorization: Bearer <firebase_id_token> header.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/messages?since={id} |
User | Fetch messages after id |
| POST | /api/messages |
User | Send a chat message |
| GET | /api/online |
User | List online users |
| POST | /api/online |
User | Ping presence |
| POST | /api/announce |
Admin | Send highlighted announcement |
| POST | /api/admin/clear |
Admin | Clear all messages |
| POST | /api/admin/ban |
Admin | Ban a user by UID |
| POST | /api/admin/unban |
Admin | Unban a user by UID |
| GET | /api/admin/users |
Admin | List all users + banned |
| POST | /api/admin/delete-message |
Admin | Delete a specific message |
| WS | /api/ws?token={idToken} |
User | Real-time WebSocket connection |
Events received by the client over the WebSocket:
type |
Payload | Description |
|---|---|---|
message |
{ message: MessageObject } |
New chat message |
delete |
{ messageId: number } |
Message was deleted by admin |
clear |
— | Chat was cleared by admin |
presence |
{ event, displayName, onlineCount } |
User joined or left |
pong |
— | Heartbeat response |
# Start the worker locally
cd worker
wrangler dev
# Serve frontend (any static server works)
cd ../frontend
npx serve .
# or: python3 -m http.server 8080Set API_BASE in index.html to http://localhost:8787 for local dev.
| Variable | Type | Where to set | Description |
|---|---|---|---|
FIREBASE_PROJECT_ID |
Secret | wrangler secret put |
Firebase project ID |
ADMIN_UID |
Secret | wrangler secret put |
Firebase UID of the admin user |
FRONTEND_ORIGIN |
Secret | wrangler secret put |
Pages domain for CORS |
DB |
Binding | wrangler.toml |
D1 database binding |
CHAT_ROOM |
Binding | wrangler.toml |
Durable Object binding |
| v1 (old) | v2 (new) |
|---|---|
| Anonymous + optional username | Firebase auth required (email or Google) |
| Password stored in D1 (SHA-256) | Passwords managed by Firebase |
| Hardcoded admin username | Admin = matching Firebase UID in env |
| Polling every 2.5s | WebSocket — true real-time via Durable Object |
time : user : message layout |
user + timestamp header, message below |
Pages Functions (/functions/) |
Standalone Cloudflare Worker |