{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreiahquty6r5oma4e4si6kh3peun2flhddvkd75cisw2lzo3bm5kr5y",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mpq3ca26iex2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreieot2j46chodhc7fb2kw4vghzxi2ybztq5tcwfxabwmygvpr3kkpe"
    },
    "mimeType": "image/webp",
    "size": 97798
  },
  "path": "/tienbuilds/build-a-realtime-chat-app-in-5-minutes-with-pocketbase-cloud-react-172e",
  "publishedAt": "2026-07-03T07:42:28.000Z",
  "site": "https://dev.to",
  "tags": [
    "react",
    "pocketbase",
    "tutorial",
    "webdev",
    "PocketBase Cloud",
    "pocketbasecloud.com",
    "@request.auth.id"
  ],
  "textContent": "Realtime features usually mean one of two things: paying for Firebase, or wrestling with WebSocket servers at 2 AM. Today we're doing neither.\n\nWe're going to build a **live chat app** where messages appear instantly across all connected browsers — using PocketBase Cloud for the backend and React for the frontend. No servers to configure, no WebSocket code to write. PocketBase handles realtime over **Server-Sent Events (SSE)** , and the JS SDK wraps it in one function call.\n\nTotal time: about 5 minutes. Let's go. ⏱️\n\n##  What we're building\n\n  * A public chat room (no auth, to keep this short)\n  * Messages stored in PocketBase (SQLite under the hood)\n  * Realtime updates via SSE — open two tabs and watch them sync\n\n\n\n##  Step 1 — Deploy PocketBase (30 seconds, literally)\n\n  1. Go to **pocketbasecloud.com** and sign up (Free plan works fine for this tutorial)\n  2. Click **Deploy** , pick a region close to you (there are 6 — Germany, Finland, US East/West, Singapore...)\n  3. Wait ~30 seconds. You'll get a URL like:\n\n\n\n\n    https://your-app.pocketbasecloud.com\n\n\nThat's a full PocketBase instance with SSL already configured. Open `/_/` on that URL to access the admin dashboard and create your admin account:\n\n\n\n    https://your-app.pocketbasecloud.com/_/\n\n\n> Self-hosting fans: everything below works identically on a self-hosted instance. The cloud just skips the VPS + nginx + certbot ritual.\n\n##  Step 2 — Create the `messages` collection\n\nIn the admin dashboard:\n\n  1. **Collections → New collection** → name it `messages` (type: Base)\n  2. Add two fields:\n     * `username` — Plain text, required\n     * `text` — Plain text, required\n  3. Open the **API Rules** tab and set both **List/Search rule** and **View rule** and **Create rule** to empty (unlock them) so anyone can read and post.\n\n\n\n⚠️ Empty rules = public access. Fine for a demo chat; for a real app you'd lock Create to authenticated users with a rule like `@request.auth.id != \"\"`.\n\nThat's the entire backend. No migrations, no ORM, no REST controllers.\n\n##  Step 3 — Scaffold the React app\n\n\n    npm create vite@latest pb-chat -- --template react\n    cd pb-chat\n    npm install pocketbase\n    npm run dev\n\n\n##  Step 4 — The realtime magic\n\nReplace `src/App.jsx` with this (~60 lines, that's the whole app):\n\n\n\n    import { useEffect, useRef, useState } from \"react\";\n    import PocketBase from \"pocketbase\";\n\n    // 👇 your PocketBase Cloud URL\n    const pb = new PocketBase(\"https://your-app.pocketbasecloud.com\");\n\n    export default function App() {\n      const [messages, setMessages] = useState([]);\n      const [text, setText] = useState(\"\");\n      const [username] = useState(\n        () => \"guest-\" + Math.random().toString(36).slice(2, 7)\n      );\n      const bottomRef = useRef(null);\n\n      useEffect(() => {\n        // 1. Load the latest 50 messages\n        pb.collection(\"messages\")\n          .getList(1, 50, { sort: \"created\" })\n          .then((res) => setMessages(res.items));\n\n        // 2. Subscribe to realtime changes (SSE under the hood)\n        pb.collection(\"messages\").subscribe(\"*\", (e) => {\n          if (e.action === \"create\") {\n            setMessages((prev) => [...prev, e.record]);\n          }\n        });\n\n        // 3. Clean up the subscription on unmount\n        return () => pb.collection(\"messages\").unsubscribe(\"*\");\n      }, []);\n\n      useEffect(() => {\n        bottomRef.current?.scrollIntoView({ behavior: \"smooth\" });\n      }, [messages]);\n\n      async function send(e) {\n        e.preventDefault();\n        if (!text.trim()) return;\n        await pb.collection(\"messages\").create({ username, text });\n        setText(\"\"); // no manual state update needed — SSE delivers it back\n      }\n\n      return (\n        <div style={{ maxWidth: 480, margin: \"40px auto\", fontFamily: \"sans-serif\" }}>\n          <h2>⚡ PB Cloud Chat — you are {username}</h2>\n          <div style={{ height: 360, overflowY: \"auto\", border: \"1px solid #ddd\", padding: 12, borderRadius: 8 }}>\n            {messages.map((m) => (\n              <p key={m.id}>\n                <b>{m.username}:</b> {m.text}\n              </p>\n            ))}\n            <div ref={bottomRef} />\n          </div>\n          <form onSubmit={send} style={{ display: \"flex\", gap: 8, marginTop: 12 }}>\n            <input\n              value={text}\n              onChange={(e) => setText(e.target.value)}\n              placeholder=\"Type a message…\"\n              style={{ flex: 1, padding: 8 }}\n            />\n            <button>Send</button>\n          </form>\n        </div>\n      );\n    }\n\n\nRun it, open **two browser tabs** , and send a message. Both tabs update instantly. 🎉\n\nNotice the neat trick in `send()`: we don't append the message to local state ourselves. We just `create()` the record, and the realtime subscription delivers it back to _every_ client — including the sender. One source of truth, zero sync bugs.\n\n##  What's actually happening under the hood?\n\nPocketBase's realtime API uses **Server-Sent Events** , not WebSockets:\n\n  1. The SDK opens a long-lived HTTP connection to `/api/realtime`\n  2. It submits which collections/records you want to watch\n  3. The server pushes JSON events (`create` / `update` / `delete`) down that stream\n\n\n\nSSE is simpler than WebSockets (plain HTTP, auto-reconnect built into the browser) and passes through proxies and firewalls more reliably. For server→client pushes like chat, feeds, and dashboards, it's honestly all you need.\n\n##  Step 5 — Ship it\n\nBuild the frontend:\n\n\n\n    npm run build\n\n\nPocketBase Cloud includes **frontend hosting** on every plan (even Free), so you can deploy the `dist/` folder right next to your backend — one dashboard, one URL, no CORS headaches. Or push it to any static host you like; the backend URL is public either way.\n\n##  Where to go from here\n\n  * 🔐 Add auth: PocketBase ships with email/password + OAuth2 (Google, GitHub...) built in — then lock the Create rule to `@request.auth.id != \"\"`\n  * 🗑️ Handle `update`/`delete` events in the subscription for message editing\n  * 📄 Paginate history with `getList(page, perPage)`\n  * 🚀 Running multiple side projects? The Pro plan (from $13/mo) lets you run **unlimited PocketBase instances on one dedicated server** — many users fit 10–20 apps on it\n\n\n\nTry it here 👉 **pocketbasecloud.com** — the Free plan is genuinely 30 seconds to a live backend.\n\nIf you build something with this, drop a link in the comments. I'd love to see it! 💬",
  "title": "Build a Realtime Chat App in 5 Minutes with PocketBase Cloud + React"
}