Skip to content

Repository files navigation

💬 Chatly Platform

A production-grade, full-stack realtime chat application.

Live Demo: chatapp.ashishpatil.me

TypeScript React Socket.IO Supabase Redis Tailwind CSS License: MIT

Sub-100ms message delivery · Supabase PostgreSQL & S3 Storage · Dark Theme · Responsive Mobile UI · JWT auth with refresh token rotation · Optimistic UI · Redis pub/sub · Emoji reactions · Read receipts

Live Demo · Quick Start · Architecture · API Reference · Deployment


Table of Contents


Features

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)

Presence & Status

  • 🟢 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

Security & Auth

  • 🔐 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

Infrastructure & Cloud

  • ⚡ 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

Tech Stack

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

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                         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  │
              └────────────────────────────┘

Project Structure

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

Quick Start

Prerequisites

Tool Version Install
Node.js 20+ nodejs.org
Docker 24+ docker.com
Git Any git-scm.com

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

Demo Accounts

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.


Environment Variables

Copy .env.example to .env. All variables are documented inline.

Server Variables

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)

API Reference

All routes are prefixed with /api/v1.

Auth — POST /api/v1/auth

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 }

Socket.IO Events

Client → Server

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

Server → Client

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

Database Schema

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 }

Testing

# 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 Coverage

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

License

MIT © 2026 Chatly Platform

About

Production-grade full-stack real-time chat platform. React 18 + TypeScript frontend · Node.js/Express REST API · Socket.IO · PostgreSQL · Redis · Docker · JWT auth. Live at chatapp.ashishpatil.me

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages