# Realtime Chat

> Build a full-featured chat app with Aerostack. Real-time messaging, history loading, and online presence in under 100 lines of code.

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

```
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 event
```

## Client code

```tsx

interface Message {
  id: string
  userId: string
  text: string
  timestamp: number
}

  const { realtime } = useAerostack()
  const [messages, setMessages] = useState([])
  const [inputText, setInputText] = useState('')
  const bottomRef = useRef(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>

      <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

```ts

const app = new Hono()

// Setup (run once): create tables
app.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 message
app.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 })
})

```

## Setup

```bash
cd examples/socket-hub/server
npm run dev

# Run setup to create tables
curl -X POST http://localhost:8787/setup
```

Then open the client at `http://localhost:5173` in multiple browser tabs.

The channel name `chat/general` uses a slash, so it's a custom pub/sub channel — not a DB table subscription. Messages only arrive when your server explicitly calls `sdk.socket.emit()`.
