Core Chat & Design System
⚡ Realtime messaging — Sub-100ms delivery via Socket.IO with WebSocket transport
🔄 Truly Optimistic UI — Instant message rendering, reply quotes, reactions, edits, and deletes with server reconciliation
🌙 Dark Theme — Seamless Dark/Light mode toggle with persistent storage and system theme detection
📱 Fully Responsive UI — Built for mobile devices (< 768px) with full-width views, mobile back navigation, and touch targets (≥ 44px)
💬 Direct Messages & Group Rooms — Create DMs or multi-member group channels
📜 Cursor-based pagination — Infinite scroll upward through message history (50 messages per page)
Rich Interactions & Media
😄 Emoji reactions — Optimistic reactions toggle with realtime sync across clients
↩️ Threaded replies — Reply to specific messages with optimistic quote preview
✏️ Edit & delete — Instant soft-delete and inline edit with (edited) badge
📎 File attachments — Drag-and-drop uploads via pre-signed PUT URLs compatible with Supabase S3 Storage / AWS S3 (max 25MB)
🟢 Online/Offline indicators — Live presence via Redis SETEX with 30s TTL + heartbeat
⌨️ Typing indicators — Debounced emit, auto-clear after 3 seconds
✅ Read receipts — Per-message delivery status, batched upsert every 2 seconds
🔐 JWT access tokens — 15-minute expiry, RS256-compatible secret
🔁 Refresh token rotation — 7-day HTTPOnly sameSite=strict cookie, Redis blacklist on rotation
🚫 Rate limiting — 10 req/15min on auth endpoints, 100 req/min on API (production only)
🛡️ Helmet.js — Secure HTTP headers out of the box
✅ Zod validation — All inputs validated with typed schemas in the shared package
⚡ Supabase PostgreSQL — Managed Postgres with direct migration support (DIRECT_URL) and transaction connection pooling (DATABASE_URL)
🪣 Supabase S3 Storage — Direct S3-compatible pre-signed upload URL generation (AWS_S3_ENDPOINT)
🔀 Redis pub/sub — @socket.io/redis-adapter for horizontal scaling across multiple server nodes
🗄️ Prisma ORM — Type-safe DB queries with migrations
🐳 Docker Compose — One-command local environment with Postgres + Redis + Server + Client
📊 Structured logging — Pino JSON logger with request correlation
🔄 24h auto-reset — Demo accounts and data automatically reset every 24 hours
Layer
Technology
Purpose
Frontend
React 18 + Vite + TypeScript
UI framework
Styling
Tailwind CSS v3 + Custom Dark Theme
Design tokens with light/dark theme variables
State
Zustand
Client-side state & theme management
HTTP Client
Axios + auto-refresh interceptor
API communication
Realtime
Socket.IO Client 4.7
WebSocket messaging
Backend
Node.js + Express 4 + TypeScript
REST API + Socket.IO server
Auth
JWT (jsonwebtoken) + bcryptjs
Token-based auth
Database
Supabase PostgreSQL / Local Postgres
Primary relational data store
ORM
Prisma 5 (url & directUrl)
Database access & migrations layer
Cache / Pub-Sub
Redis 7 + ioredis
Sessions, presence, scaling
Validation
Zod (shared package)
Runtime schema validation
File Storage
Supabase S3 Bucket / AWS S3
S3-compatible pre-signed URL uploads
Logging
Pino + pino-pretty
Structured JSON logging
Testing
Vitest + Supertest
Unit + Supabase DB integration tests
Containerization
Docker + Docker Compose
Local dev + production
Reverse Proxy
Nginx
SPA routing + API/Socket proxy
┌─────────────────────────────────────────────────────────────────┐
│ Client (React) │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────────────┐ │
│ │ Zustand │ │ Axios + Auto │ │ Socket.IO Client │ │
│ │ (State/Theme)│ │ Token Refresh│ │ auth getter fn │ │
│ └─────────────┘ └──────────────┘ └────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────────┘
│ HTTPS / WSS
┌────────▼────────┐
│ Nginx (port 80) │
│ SPA + API proxy │
└────────┬────────┘
│
┌─────────────▼──────────────┐
│ Node.js Express Server │
│ │
│ ┌─────────┐ ┌──────────┐ │
│ │ REST │ │Socket.IO │ │
│ │ API v1 │ │ Server │ │
│ └────┬────┘ └────┬─────┘ │
└───────┼──────────┼────────┘
│ │ Redis Adapter
┌───────▼──────────▼────────┐
│ Redis 7 │
│ • Session blacklist │
│ • Pub/Sub (multi-node) │
│ • Presence TTL keys │
└───────────────────────────┘
┌────────────────────────────┐
│ Supabase PostgreSQL │
│ • Direct URL (Migrations) │
│ • Pooled URL (Queries) │
│ • Users, Rooms, Messages │
└────────────────────────────┘
realtime-chat/
├── apps/
│ ├── client/ # React + Vite + TypeScript
│ │ ├── src/
│ │ │ ├── components/
│ │ │ │ ├── chat/ # ChatArea, Sidebar, MessageItem, MessageInput
│ │ │ │ ├── common/ # Button, Input
│ │ │ │ └── modals/ # CreateRoomModal
│ │ │ ├── pages/ # AuthPage, ChatPage
│ │ │ ├── services/ # api.ts (Axios), socket.ts (Socket.IO)
│ │ │ ├── store/ # useAuthStore, useChatStore, useThemeStore (Zustand)
│ │ │ └── styles/ # globals.css (Tailwind + CSS variables)
│ │ ├── public/ # favicon.svg, robots.txt, sitemap.xml
│ │ ├── index.html # Full SEO meta tags + JSON-LD
│ │ ├── nginx.conf # Production SPA routing + WebSocket proxy
│ │ ├── Dockerfile # Multi-stage: Vite build → nginx:alpine
│ │ └── vite.config.ts # Dev proxy + manual chunk splitting
│ │
│ └── server/ # Node.js + Express + Socket.IO
│ ├── src/
│ │ ├── config/ # env.ts (Zod-validated env vars)
│ │ ├── controllers/ # auth, room, user, upload (AWS/Supabase S3)
│ │ ├── middlewares/ # auth.middleware.ts (JWT)
│ │ ├── routes/ # auth, room, user, upload routes
│ │ ├── sockets/ # socket.manager.ts (all Socket.IO logic)
│ │ ├── tests/ # jwt.test.ts, shared.test.ts, supabase-db.test.ts (Vitest)
│ │ └── utils/ # jwt, logger, prisma, redis, testDataScheduler
│ ├── prisma/
│ │ ├── schema.prisma # Full data model (url & directUrl)
│ │ └── seed.ts # Test account seeding + 24h auto-reset
│ ├── vitest.config.ts # Test config
│ ├── Dockerfile # Multi-stage: TS builder → node:alpine
│ └── tsconfig.json
│
├── packages/
│ └── shared/ # Shared between client & server
│ └── src/index.ts # Zod schemas, TypeScript DTOs, Socket contracts
│
├── docker-compose.yml # All services: postgres, redis, server, client
├── .env.example # Documented environment template with Supabase config
├── DEPLOYMENT.md # Step-by-step deployment guide
└── README.md # This file
Option A — Docker Compose (Recommended)
# 1. Clone the repository
git clone https://github.com/your-username/realtime-chat.git
cd realtime-chat
# 2. Set up environment variables
cp .env.example .env
# 3. Start all 4 services
docker compose up --build
# 4. Open the app
open http://localhost:80
Option B — Local Development with Supabase PostgreSQL
# 1. Clone and install
git clone https://github.com/your-username/realtime-chat.git
cd realtime-chat
npm install
# 2. Set up environment
cp .env.example .env
# Set DATABASE_URL and DIRECT_URL to your Supabase PostgreSQL credentials in .env
# 3. Set up database & generate Prisma client
cd apps/server
npx prisma generate
npx prisma db push # Synchronizes schema with Supabase
npm run prisma:seed # Seeds demo test accounts
# 4. Start client & server
cd ../..
npm run dev:server # Terminal 1 — http://localhost:5000
npm run dev:client # Terminal 2 — http://localhost:5173
Live Demo: chatapp.ashishpatil.me
Three test accounts are pre-seeded and automatically reset every 24 hours :
User
Email
Password
👩 Alice
alice@example.com
Password123!
👨 Bob
bob@example.com
Password123!
🧑 Charlie
charlie@example.com
Password123!
Tip: Use the Quick demo login buttons on the landing page to auto-fill credentials, or open multiple browser tabs/windows to simulate multi-user realtime chat.
Copy .env.example to .env. All variables are documented inline.
Variable
Default
Required
Description
NODE_ENV
development
development | production | test
PORT
5000
Server listen port
CLIENT_URL
http://localhost:5173
✅
Frontend origin (CORS allowlist)
DATABASE_URL
(see .env.example)
✅
Supabase Pooled / PostgreSQL connection string
DIRECT_URL
(see .env.example)
✅
Direct PostgreSQL connection string for Prisma migrations
REDIS_URL
redis://localhost:6379
✅
Redis connection string
JWT_SECRET
(see .env.example)
✅
Access token signing secret (32+ bytes)
JWT_REFRESH_SECRET
(see .env.example)
✅
Refresh token signing secret (32+ bytes)
AWS_REGION
us-east-1
S3 / Supabase Storage region
AWS_S3_BUCKET
chat-attachments
Storage bucket name
AWS_ACCESS_KEY_ID
mock
S3 / Supabase S3 Access Key ID
AWS_SECRET_ACCESS_KEY
mock
S3 / Supabase S3 Secret Access Key
AWS_S3_ENDPOINT
(optional)
Supabase S3 endpoint (e.g. https://<ref>.supabase.co/storage/v1/s3)
Client Variables (Vite — baked at build time)
Variable
Default
Description
VITE_API_URL
(empty)
API base URL (leave empty in dev, Vite proxies /api)
VITE_SOCKET_URL
(empty)
Socket.IO server URL (leave empty in dev)
All routes are prefixed with /api/v1.
Method
Endpoint
Auth
Body
Response
POST
/auth/register
—
{ username, email, password }
{ user, accessToken }
POST
/auth/login
—
{ email, password }
{ user, accessToken }
POST
/auth/refresh
Cookie
—
{ accessToken }
POST
/auth/logout
—
—
{ message }
GET
/auth/me
Bearer
—
{ user }
Rooms — GET/POST /api/v1/rooms
Method
Endpoint
Description
GET
/rooms
List all rooms the authenticated user is a member of
POST
/rooms
Create a new group room { name, memberIds[] }
POST
/rooms/dm/:targetUserId
Get or create a DM room with a user
GET
/rooms/:id/messages
Fetch messages with cursor pagination (?before=msgId&limit=50)
Users — GET /api/v1/users
Method
Endpoint
Description
GET
/users/search?q=
Search users by username or email
Upload — POST /api/v1/upload
Method
Endpoint
Body
Response
POST
/upload/presigned
{ filename, mimeType, size }
{ uploadUrl, fileUrl, key, bucket }
Event
Payload
Description
join_room
roomId: string
Join a Socket.IO room to receive its events
leave_room
roomId: string
Leave a Socket.IO room
send_message
{ roomId, content, type, replyToId?, tempId?, attachments? }
Send a message (ack callback returns { status, message })
typing_start
roomId: string
Broadcast that user is typing
typing_stop
roomId: string
Broadcast that user stopped typing
mark_read
{ roomId, messageId }
Mark a message as read
edit_message
{ messageId, content }
Edit a message (sender only)
delete_message
messageId: string
Soft-delete a message (sender only)
add_reaction
{ messageId, emoji }
Toggle an emoji reaction
Event
Payload
Description
message_new
MessageDTO
New message in a room (includes tempId for reconciliation)
message_updated
MessageDTO
Message was edited
message_deleted
{ messageId, roomId }
Message was soft-deleted
typing_update
{ roomId, userId, username, isTyping }
Typing status changed
read_update
{ roomId, userId, messageId, readAt }
Read receipt recorded
user_presence
{ userId, status }
User came online or went offline
reaction_update
{ messageId, roomId, reactions[] }
Reactions on a message changed
error
{ code, message }
Server-side error
model User { id, username, email, passwordHash, avatar, status, lastSeen, createdAt }
model Room { id, name, type(DM|GROUP), avatar, createdBy, createdAt }
model RoomMember { userId, roomId, role(ADMIN|MEMBER), joinedAt, lastRead }
model Message { id, roomId, senderId, content, type, replyToId, tempId, editedAt, deletedAt, createdAt }
model Attachment { id, messageId, url, name, size, mimeType }
model Reaction { id, messageId, userId, emoji } // unique(messageId, userId, emoji)
model ReadReceipt { userId, messageId, roomId, readAt }
# Run unit & Supabase DB integration tests (11 tests across 3 test files)
cd apps/server
npm test
# TypeScript type check (server)
npx tsc --noEmit
# TypeScript type check (client)
cd apps/client
npx tsc --noEmit
Test File
Tests
Covers
supabase-db.test.ts
4
Supabase PostgreSQL raw queries, seeded user lookups, relation joins, and CRUD transactions
shared.test.ts
4
Register schema, login schema, message schema, empty content validation
jwt.test.ts
3
Access token generate/verify, refresh token, invalid token throws
MIT © 2026 Chatly Platform