Realtime chat rooms with no signup and a visible ten-minute expiry.
Live demo · Architecture · Security policy
PingMe is a full-stack portfolio project built around a simple constraint: some conversations should be easy to start and intentionally short-lived. A participant creates a six-character room, shares the link, and chats through Redis-backed realtime events. The first join starts a server-backed ten-minute TTL shared by room metadata, membership, and messages.
- End-to-end product flow across a Next.js client, typed Elysia API, Redis, and realtime delivery
- Runtime validation with Zod at room, identity, message, and reaction boundaries
- Per-room HTTP-only session cookies and server-bound display identities
- TTL-based lifecycle management for pending and active rooms
- Realtime messages, reactions, typing state, participant events, and room destruction
- Responsive UI with keyboard focus, reduced-motion support, empty/loading/error states, and dark mode
- Truthful privacy boundaries: temporary retention without claiming end-to-end encryption
- Create a room. PingMe stores pending metadata for up to one hour.
- Share the generated link or six-character code.
- The first participant joins and starts the ten-minute room timer.
- Participants exchange messages and reactions through realtime events.
- Redis TTLs remove room, membership, and message keys when time expires—or a participant closes the room early.
Browser
├─ Next.js App Router UI
├─ TanStack Query cache
└─ Upstash Realtime client
│
▼
Typed Elysia routes (/api)
├─ Zod input contracts
├─ per-room HTTP-only auth cookie
└─ server-bound temporary identity
│
▼
Upstash Redis + Realtime
├─ meta:{roomID}
├─ members:{roomID}
├─ connected:{roomID}
└─ messages:{roomID}
shared active-room TTL
See ARCHITECTURE.md for data flow, key design, failure handling, and trade-offs.
- Next.js 16, React 19, and TypeScript
- Elysia for the API layer and a small typed fetch client for browser requests
- TanStack Query for server-state orchestration
- Upstash Redis and Upstash Realtime
- Zod for runtime schemas
- Tailwind CSS 4 and Framer Motion
- Bun test, ESLint, and the TypeScript compiler
Requirements: Node.js 20+ or Bun, plus an Upstash Redis database.
git clone https://github.com/marsh15/pingme.git
cd pingme
npm install
cp .env.example .env.localFill in the Redis URL and token in .env.local, then start the app:
npm run devOpen http://localhost:3000.
| Variable | Required | Purpose |
|---|---|---|
UPSTASH_REDIS_REST_URL |
Yes | Upstash Redis REST endpoint |
UPSTASH_REDIS_REST_TOKEN |
Yes | Upstash Redis REST credential |
NEXT_PUBLIC_APP_URL |
Recommended | Canonical deployment URL; use http://localhost:3000 locally |
Legacy KV_REST_API_URL and KV_REST_API_TOKEN names are also accepted by the configuration validator.
npm test
npm run lint
npx tsc --noEmit
npm run buildThe unit suite covers configuration fallbacks, room-code normalization, safe identity/message contracts, reaction allowlisting, and per-room cookie/key scoping.
PingMe reduces retention; it is not an end-to-end encrypted messenger. Messages are processed by the application and stored as plaintext in Redis until the room expires. A room code is an invitation, not a strong access-control secret. Do not use PingMe for credentials, payment data, medical information, or other sensitive content.
Security-relevant implementation details:
- The browser never receives membership tokens in message history or realtime payloads.
- A server-side room membership record binds each token to one temporary display name.
- Each room uses a separate HTTP-only,
SameSite=Laxcookie. - API schemas trim and bound message content and allowlist reactions.
- Active keys receive the remaining server-side room TTL.
See SECURITY.md for reporting guidance and known limitations.
- Redis lists keep the implementation compact, but reaction/delete updates are
O(n)scans and are suited to small rooms. - The 50-person limit is a product guardrail, not a distributed rate limit.
- Delivery is realtime but not guaranteed; clients refetch canonical message history after events.
- Temporary deletion relies on Redis TTL guarantees and application-level delete requests, not cryptographic erasure.