Realtime Chat
A simple channel-based chat room. Messages are sent via HTTP to the server, persisted to D1, and broadcast to all WebSocket subscribers.
Demonstrates: basic pub/sub, HTTP + WebSocket together, multi-tab sync
Source: examples/socket-hub/src/examples/RealtimeChat/
How it works
Section titled “How it works”User types message → POST /examples/chat/send (HTTP) → Server: sdk.db.query INSERT INTO messages → Server: sdk.socket.emit('chat:message', message, roomId) → All subscribers on channel('general') receive the eventClient code
Section titled “Client code”import { useAerostack } from '@aerostack/react'
interface Message { id: string userId: string text: string timestamp: number}
export function RealtimeChat() { const { realtime } = useAerostack() const [messages, setMessages] = useState<Message[]>([]) const [inputText, setInputText] = useState('') const bottomRef = useRef<HTMLDivElement>(null)
const roomId = 'general' const userId = useRef(`user-${Math.random().toString(36).slice(2, 8)}`)
useEffect(() => { const channel = realtime.channel(`chat/${roomId}`)
channel .on('*', ({ data }) => { if (data && data.text) { setMessages(prev => { // Avoid duplicate if we already added it optimistically if (prev.find(m => m.id === data.id)) return prev return [...prev, data] }) } }) .subscribe()
return () => channel.unsubscribe() }, [realtime])
// Auto-scroll to bottom useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [messages])
const sendMessage = async () => { if (!inputText.trim()) return
const message: Message = { id: Date.now().toString(), userId: userId.current, text: inputText, timestamp: Date.now(), }
// Optimistic update setMessages(prev => [...prev, message]) setInputText('')
// Send to server await fetch('/api/examples/chat/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ roomId, ...message }), }) }
return ( <div className="flex flex-col h-screen max-w-2xl mx-auto p-4"> <div className="flex-1 overflow-y-auto space-y-2 mb-4"> {messages.map(msg => ( <div key={msg.id} className={`flex ${msg.userId === userId.current ? 'justify-end' : 'justify-start'}`} > <div className={`max-w-xs px-4 py-2 rounded-2xl text-sm ${ msg.userId === userId.current ? 'bg-blue-600 text-white' : 'bg-gray-800 text-gray-100' }`}> {msg.userId !== userId.current && ( <div className="text-xs text-gray-400 mb-1">{msg.userId}</div> )} {msg.text} </div> </div> ))} <div ref={bottomRef} /> </div>
<form onSubmit={e => { e.preventDefault(); sendMessage() }} className="flex gap-2" > <input value={inputText} onChange={e => setInputText(e.target.value)} placeholder="Type a message..." className="flex-1 bg-gray-900 border border-gray-700 rounded-xl px-4 py-2 text-white" /> <button type="submit" className="bg-blue-600 text-white px-6 py-2 rounded-xl font-medium" > Send </button> </form> </div> )}Server code
Section titled “Server code”import { Hono } from 'hono'import { sdk } from '@aerostack/sdk'
const app = new Hono()
// Setup (run once): create tablesapp.post('/setup', async (c) => { await sdk.db.exec(` CREATE TABLE IF NOT EXISTS messages ( id TEXT PRIMARY KEY, room_id TEXT NOT NULL, user_id TEXT NOT NULL, text TEXT NOT NULL, created_at INTEGER NOT NULL ) `) return c.json({ ok: true })})
// Send a chat messageapp.post('/examples/chat/send', async (c) => { const { roomId, id, userId, text, timestamp } = await c.req.json()
// Persist to DB await sdk.db.query( 'INSERT INTO messages (id, room_id, user_id, text, created_at) VALUES (?, ?, ?, ?, ?)', [id, roomId, userId, text, timestamp] )
// Broadcast to all subscribers sdk.socket.emit('chat:message', { id, userId, text, timestamp }, `chat/${roomId}`)
return c.json({ ok: true })})
export default appcd examples/socket-hub/servernpm run dev
# Run setup to create tablescurl -X POST http://localhost:8787/setupThen open the client at http://localhost:5173 in multiple browser tabs.