A real-time, text-only chat app with a Discord-style UI — built to show off Convex.
Servers, categorized channels, DMs, reactions, presence, typing indicators and file uploads — every message, reaction and status updates live on every screen, with no refresh and no websocket plumbing.
Features · Screenshots · How it works · Run it · Tech
Note: Echo is a personal learning project — a UI-style homage to Discord built to explore Convex + Better Auth. It is not affiliated with Discord and ships none of Discord's logos, brand assets, or code.
Real-time chat is the perfect stress test for a reactive backend — and Convex makes it almost free. Every list below is a plain reactive query; updates fan out to all clients automatically.
| Feature | Convex primitive |
|---|---|
| 🔄 Live messages | Reactive useQuery (paginated) — new messages appear instantly for everyone in the channel/DM. |
| ⌨️ Typing indicators | A short-lived typing table; clients see "X is typing…" live and it self-expires. |
| 🟢 Presence | A heartbeat mutation updates lastSeen; the member list shows online / idle / dnd / offline in real time. |
| 😀 Reactions | A reactions table + toggle mutation; counts update live across clients. |
| 📎 File / image attachments | Convex file storage (generateUploadUrl + ctx.storage). |
| 💬 Direct messages | 1:1 conversations alongside servers, find-or-create by a sorted-id key. |
| 🧹 Scheduled cleanup | A cron prunes stale typing/presence rows every 30 minutes. |
| 🔐 Auth | Better Auth running inside Convex (email + password), wired for SSR with TanStack Start. |
| DM home | A conversation (with profile panel) |
|---|---|
![]() |
![]() |
A message sent by another client appears instantly, and you can see who's typing — no refresh.
- Sign in (Better Auth, email + password). A lightweight
profileis created on first login. - Create or join a server with a short invite code. Each server has categories of text channels.
- Chat in a channel: messages, emoji reactions, "is typing…", and image/file uploads — all live.
- DM anyone: open a 1:1 conversation from the Direct Messages list.
- The member list shows everyone grouped by role, with live presence dots.
- Convex — reactive database + serverless functions (queries, mutations, file storage, scheduled crons)
- Better Auth via @convex-dev/better-auth — auth running inside Convex
- TanStack Start (React 19) — full-stack framework, file-based routing, SSR
- Tailwind CSS v4 — Discord-style dark theme via
@themetokens - Bun — package manager & runtime
Requires Bun and the Convex CLI.
bun install
# 1. Start Convex — creates a local (or cloud) deployment and writes the URLs to .env.local
convex dev
# 2. Set the auth env vars on the deployment (in another terminal)
convex env set BETTER_AUTH_SECRET "$(openssl rand -base64 32)"
convex env set SITE_URL "http://localhost:3000"
# 3. Add VITE_SITE_URL=http://localhost:3000 to .env.local, then run the frontend
bun run dev # http://localhost:3000Open http://localhost:3000, create an account, and start a server. Want it to look populated?
Seed a demo server (cast of channels, members, messages, reactions and a couple of DMs):
convex run seed:seed '{}' # attaches the demo server to your signed-in account💡 The seeded "members" are display-only profiles (no real accounts). To see live cross-client updates with real identities, open the app in two browsers and sign in as two users.
convex/
schema.ts # profiles, servers, members, categories, channels, dms, messages, reactions, presence, typing
auth.ts # Better Auth component + createAuth
auth.config.ts # Convex auth provider for Better Auth
http.ts # Better Auth HTTP routes
convex.config.ts # registers the betterAuth component
users.ts # getMe / ensureProfile / updateProfile / searchUsers
servers.ts # create/join servers, channels, member list (with presence)
messages.ts # paginated list, send, edit, delete, uploads
reactions.ts # toggle emoji reactions
dms.ts # open/list direct messages
presence.ts # heartbeat, typing, listTyping
crons.ts # periodic cleanup of stale typing/presence
seed.ts # demo data
src/
routes/ # TanStack Start file-based routes
login.tsx
app.tsx # auth gate + server rail (+ heartbeat)
app.me.tsx / app.me.$dmId.tsx # DM home + conversations
app.guild.$serverId.$channelId.tsx # server channel view
api/auth/$.ts # Better Auth request handler
components/discord/ # ServerRail, ChannelSidebar, DMSidebar, ChatArea, MessageList, MemberList, ProfilePanel, …
lib/ # auth-client, auth-server, formatting helpers




