A real-time chat application built for 50+ concurrent users
Features Β· Architecture Β· Getting Started Β· API Reference Β· WebSocket Events Β· Contributing
- β‘ Real-time messaging via persistent WebSocket connections
- π Public & private rooms β create, join, leave, and invite users via shareable links
- π¬ Direct messages (DMs) β one-on-one conversations with contacts
- π₯ Contact system β send, accept, and reject contact requests
- βοΈ Typing indicators β live "user is typingβ¦" notifications
- π’ Online presence β see who's currently active across all rooms
- π§βπΌ User profiles β display name and profile picture support
- π Persistent message history β stored in PostgreSQL, loaded on room join
- π΄ Unread counts β per-room badge counts for missed messages
- π JWT-based WebSocket auth β all connections authenticated via Appwrite-issued JWTs
βββββββββββββββββββββββββββββββββββ
β React Client β
β (Vite + Appwrite SDK) β
β β
β Auth ββββββββ Appwrite β
β Realtime βββββ WebSocket β
β Data ββββββββ Express REST β
ββββββββββββββ¬βββββββββββββββββββββ
β HTTP + WebSocket
ββββββββββββββΌβββββββββββββββββββββ
β Express Server β
β (Node.js + ws library) β
β β
β REST API ββββ PostgreSQL β
β WebSocket ββββ in-memory Maps β
β Auth verify ββ Appwrite API β
ββββββββββββββ¬βββββββββββββββββββββ
β
ββββββββββ΄βββββββββ
β β
βββββΌβββββ ββββββββΌβββββββ
β PG DB β β Appwrite β
β msgs β β auth/users β
β rooms β βββββββββββββββ
βcontactsβ
ββββββββββ
The server maintains two in-memory structures for real-time routing:
onlineUsers: Map<userId, Set<WebSocket>>β all active connections per userrooms: Map<roomId, Set<WebSocket>>β all subscribers per room
- Node.js >= 18.0.0
- PostgreSQL database β local, or hosted (Supabase, Neon, Railway)
- Appwrite project β cloud.appwrite.io or self-hosted, with Email/Password auth enabled
git clone https://github.com/your-username/realtime-chat-app.git
cd realtime-chat-app- Create a project at cloud.appwrite.io.
- Enable Email/Password authentication under Auth β Settings.
- Copy your Project ID and API Endpoint β you'll need them in both
.envfiles below.
cd server
cp .env.example .env # then fill in your values
npm installserver/.env reference
# Server
PORT=10000
# PostgreSQL
PGUSER=your_db_user
PGHOST=your_db_host
PGDATABASE=your_db_name
PGPASSWORD=your_db_password
PGPORT=5432
# Appwrite (used to verify JWTs)
APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
APPWRITE_PROJECT_ID=your_appwrite_project_id
# CORS β comma-separated list of allowed frontend origins
CORS_ORIGIN=http://localhost:5173Run the following SQL against your PostgreSQL database before starting the server:
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
name TEXT,
status TEXT DEFAULT 'offline',
last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS rooms (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
type TEXT DEFAULT 'public',
created_by TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS room_members (
room_id TEXT REFERENCES rooms(id),
user_id TEXT REFERENCES users(id),
last_read_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (room_id, user_id)
);
CREATE TABLE IF NOT EXISTS messages (
id SERIAL PRIMARY KEY,
room_id TEXT REFERENCES rooms(id),
user_id TEXT,
user_email TEXT,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS contacts (
id SERIAL PRIMARY KEY,
user_id TEXT REFERENCES users(id),
contact_id TEXT REFERENCES users(id),
status TEXT DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (user_id, contact_id)
);cd ../client
cp .env.example .env # then fill in your values
npm installclient/.env reference
VITE_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
VITE_APPWRITE_PROJECT_ID=your_appwrite_project_id
VITE_API_URL=http://localhost:10000
VITE_WS_URL=ws://localhost:10000Start the server (terminal 1):
cd server
npm run dev # nodemon β auto-reloads on save
# or
npm start # plain nodeStart the client (terminal 2):
cd client
npm run devOpen http://localhost:5173 in your browser. π
| Layer | Recommended platforms |
|---|---|
| Server | Railway, Render, Fly.io |
| Client | Vercel, Netlify, Cloudflare Pages |
| Database | Supabase, Neon, Railway |
After deploying the server, update the client's
VITE_API_URLandVITE_WS_URLto your server's public URL, and add the client's origin to the server'sCORS_ORIGINenv var.
| Variable | Required | Default | Description |
|---|---|---|---|
PORT |
No | 10000 |
HTTP / WebSocket server port |
PGUSER |
Yes | β | PostgreSQL username |
PGHOST |
Yes | β | PostgreSQL host |
PGDATABASE |
Yes | β | PostgreSQL database name |
PGPASSWORD |
Yes | β | PostgreSQL password |
PGPORT |
No | 5432 |
PostgreSQL port |
APPWRITE_ENDPOINT |
Yes | http://localhost/v1 |
Appwrite API endpoint |
APPWRITE_PROJECT_ID |
Yes | β | Appwrite project ID |
CORS_ORIGIN |
No | β | Comma-separated list of allowed client origins |
| Variable | Required | Default | Description |
|---|---|---|---|
VITE_APPWRITE_ENDPOINT |
Yes | β | Appwrite API endpoint |
VITE_APPWRITE_PROJECT_ID |
Yes | β | Appwrite project ID |
VITE_API_URL |
No | http://localhost:3000 |
Express REST API base URL |
VITE_WS_URL |
No | ws://localhost:3000 |
WebSocket server URL |
All REST endpoints except /health require an x-user-id header with the authenticated user's Appwrite ID.
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Server health check (uptime, users, rooms) |
| GET | /api/rooms |
List rooms the current user belongs to |
| POST | /api/rooms/create |
Create a new room |
| POST | /api/rooms/join |
Join an existing room by ID |
| POST | /api/rooms/leave |
Leave a room |
| POST | /api/rooms/dm |
Create or retrieve a DM room with another user |
| POST | /api/rooms/invite |
Generate an invite link for a room |
| POST | /api/rooms/update |
Update a room's name |
| GET | /api/messages/:roomId |
Fetch paginated message history for a room |
| GET | /api/users/search |
Search users by email (?email=...) |
| GET | /api/contacts |
List accepted contacts |
| GET | /api/contacts/requests |
List pending incoming contact requests |
| POST | /api/contacts/request |
Send a contact request to a user |
| POST | /api/contacts/accept |
Accept a contact request |
| POST | /api/contacts/reject |
Reject a contact request |
After connecting, the client must authenticate by sending a JOIN message with a valid Appwrite JWT. All messages are JSON-encoded.
| Type | Payload fields | Description |
|---|---|---|
JOIN |
token, userId, email |
Authenticate and register connection |
JOIN_ROOM |
roomId |
Subscribe to a room's messages |
ROOM_MESSAGE |
roomId, content |
Send a message to a room |
TYPING_START |
roomId |
Notify room that user is typing |
TYPING_STOP |
roomId |
Notify room that user stopped typing |
PING |
β | Keep-alive ping |
| Type | Payload fields | Description |
|---|---|---|
PONG |
β | Keep-alive response |
JOINED_ROOM |
roomId |
Confirms room subscription |
ROOM_MESSAGE |
roomId, userId, content, createdAt, userEmail |
New message in a room |
USER_JOINED |
roomId, userId, email |
A user joined the room |
USER_LEFT |
roomId, userId |
A user left or disconnected |
USER_TYPING |
roomId, userId, isTyping |
Typing indicator update |
USER_STATUS |
userId, status |
User came online or went offline |
ERROR |
message |
Error response from the server |
realtime-chat-app/
βββ client/ # React frontend (Vite)
β βββ src/
β β βββ App.jsx # Main app β auth, chat, contacts, rooms
β β βββ main.jsx # React entry point
β β βββ index.css # Global styles
β β βββ appwrite.js # Appwrite client setup
β βββ index.html
β βββ vite.config.js
β βββ .env.example
β βββ package.json
β
βββ server/ # Node.js backend
βββ index.js # Express app, REST endpoints, WebSocket handler
βββ db.js # PostgreSQL connection pool
βββ .env.example
βββ package.json
| Command | Description |
|---|---|
npm start |
Start the server with Node |
npm run dev |
Start with nodemon (auto-reload on save) |
| Command | Description |
|---|---|
npm run dev |
Start the Vite dev server |
npm run build |
Build for production β dist/ |
npm run preview |
Preview the production build locally |
Contributions are welcome! To get started:
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature-name - Commit your changes:
git commit -m 'Add some feature' - Push to your branch:
git push origin feature/your-feature-name - Open a Pull Request
For major changes, please open an issue first so we can discuss the approach.
This project is licensed under the MIT License β see the LICENSE file for details.
Made with β€οΈ Β Β·Β Report a Bug Β Β·Β Request a Feature