Chat: what you will build
A chat backend: rooms people join, message history that pages backwards, and live delivery over a socket that refuses anyone who is not a member.
By the end you will have:
POST /rooms,POST /rooms/:id/join, and a membership check every room route sharesGET /rooms/:id/messageswith cursor pagination that does not break while people are typingPOST /rooms/:id/messages, delivered to everyone connected in under a millisecondGET /rooms/:id/ws, guarded by the same auth as everything else and by room membership
The two things worth reading
Section titled “The two things worth reading”Cursor pagination, not offset. LIMIT 20 OFFSET 40 is wrong on a feed people are writing to:
between page one and page two, three new messages arrive, everything shifts, and the reader sees
three they already saw. This is the single most common bug in a chat API and the fix is not
difficult — it just has to be chosen deliberately.
The socket checks membership before it opens. auth: true proves who someone is. It does not
prove they belong in room 4f2a. The check happens in upgrade, which is the last moment before a
socket exists.
The shape
Section titled “The shape”src/ schema.ts rooms, members, messages app.ts bricks, and the socket route rooms.ts requireMember — the check every route shares routes/rooms/ index.post.ts create a room [id]/join.post.ts join one [id]/messages.get.ts history, paged [id]/messages.post.ts sendFour route files, a schema, and one shared function. Auth, validation, error shapes, request parsing and graceful shutdown you get without writing them.
Before you start
Section titled “Before you start”bun create theoven chat --db sqlite --auth basiccd chat && bun installThat gives you a working app with signup, login and password reset already mounted, which is what the rest of this assumes.