Skip to content

Latest commit

 

History

History
375 lines (286 loc) · 14.9 KB

File metadata and controls

375 lines (286 loc) · 14.9 KB

💬 realtime-chat-app

A real-time chat application built for 50+ concurrent users

Node.js React Vite PostgreSQL Appwrite WebSocket License: MIT

Features · Architecture · Getting Started · API Reference · WebSocket Events · Contributing


✨ Features

  • 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

🏗 Architecture

┌─────────────────────────────────┐
│         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 user
  • rooms: Map<roomId, Set<WebSocket>> — all subscribers per room

🚀 Getting Started

Prerequisites

1. Clone the repository

git clone https://github.com/your-username/realtime-chat-app.git
cd realtime-chat-app

2. Set up Appwrite

  1. Create a project at cloud.appwrite.io.
  2. Enable Email/Password authentication under Auth → Settings.
  3. Copy your Project ID and API Endpoint — you'll need them in both .env files below.

3. Configure the server

cd server
cp .env.example .env   # then fill in your values
npm install
server/.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:5173

Database schema

Run 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)
);

4. Configure the client

cd ../client
cp .env.example .env   # then fill in your values
npm install
client/.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:10000

5. Run the app

Start the server (terminal 1):

cd server
npm run dev     # nodemon — auto-reloads on save
# or
npm start       # plain node

Start the client (terminal 2):

cd client
npm run dev

Open http://localhost:5173 in your browser. 🎉


🌐 Deployment

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_URL and VITE_WS_URL to your server's public URL, and add the client's origin to the server's CORS_ORIGIN env var.


🔑 Environment Variables

Server (server/.env)

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

Client (client/.env)

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

📡 API Reference

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

🔌 WebSocket Events

After connecting, the client must authenticate by sending a JOIN message with a valid Appwrite JWT. All messages are JSON-encoded.

Client → Server

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

Server → Client

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

📁 Project Structure

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

🛠 Scripts

Server

Command Description
npm start Start the server with Node
npm run dev Start with nodemon (auto-reload on save)

Client

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

🤝 Contributing

Contributions are welcome! To get started:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-feature-name
  3. Commit your changes: git commit -m 'Add some feature'
  4. Push to your branch: git push origin feature/your-feature-name
  5. Open a Pull Request

For major changes, please open an issue first so we can discuss the approach.


📄 License

This project is licensed under the MIT License — see the LICENSE file for details.


Made with ❤️  ·  Report a Bug  ·  Request a Feature