Skip to content
SrunLyheangPublic

About

Chatle is a full-stack real-time chat app where users add each other as friends, then exchange direct or group messages with images, replies, edits, typing indicators, seen receipts, and one-to-one video calls — plus AI bot users you can chat with like any other contact.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

114 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Chatle

This is my final project for Computer Science A.

Chatle is a full-stack real-time chat app where users add each other as friends, then exchange direct or group messages with images, replies, edits, typing indicators, seen receipts, and one-to-one video calls — plus AI bot users you can chat with like any other contact.

Review Note

I used Claude and CodeRabbit to review parts of the code and get feedback on bugs, structure, and improvements.

Features

Accounts

  • Signup, login, and logout with JWT stored in an httpOnly cookie
  • Email verification with a 6-digit code, capped at 5 emails per account with a 30-second resend cooldown
  • Unverified accounts are removed automatically by a MongoDB TTL index
  • Profile updates, including an avatar uploaded through Cloudinary

People

  • Friend requests: send, accept, decline, cancel, and unfriend
  • Block a user, which hides them from your contacts, chats, and search and stops their realtime messages reaching you. Blocking is one-directional, so the blocked user is not affected
  • Per-viewer nicknames for other users, visible only to the person who set them
  • Pin chats to the top of your own chat list

Messaging

  • Direct messages and group conversations
  • Text, images, or both, with replies to an earlier message
  • Edit, delete, and pin messages
  • Typing indicators, online-user presence, and seen receipts
  • Groups: create, rename, add or remove participants, and leave

Calls

  • One-to-one video calls with ring, answer, decline, and end, using Socket.IO for signalling and Stream for the call itself

AI bots

  • Bot users you chat with like any other contact, backed by Groq or Gemini

Interface

  • Three themes and an English/Khmer language switcher
  • Keystroke, notification, and ringtone sound effects
  • Responsive React interface

Tech Stack

Layer Choice
Frontend React, Vite, Tailwind CSS, daisyUI
Frontend state Zustand
Routing React Router
Localization English/Khmer dictionaries in src/i18n
Backend Node.js, Express
Database MongoDB, Mongoose
Real-time updates Socket.IO
Authentication JWT in httpOnly cookies, bcrypt
Email Resend
Image uploads Cloudinary
AI providers Groq, Gemini
Video calls Stream
Protection Arcjet

Architecture

flowchart LR
    subgraph Frontend["React + Vite Frontend"]
        UI["Pages and Components"]
        Store["Zustand Stores"]
    end

    subgraph Backend["Express Backend"]
        Routes["REST API Routes"]
        Middleware["Route Middleware"]
        Controllers["Controllers"]
        Socket["Socket.IO Server"]
        SocketAuth["Socket Auth Middleware"]
        AI["AI Provider Layer"]
    end

    Mongo[("MongoDB")]
    Cloudinary[("Cloudinary")]
    Resend[["Resend"]]
    Stream[["Stream"]]
    Arcjet{{"Arcjet"}}
    Groq[["Groq"]]
    Gemini[["Gemini"]]

    UI --> Store
    Store -- "HTTP requests" --> Routes
    Store -- "Socket.IO connection" --> Socket

    Routes --> Middleware
    Middleware -- "rate limit / bot checks" --> Arcjet
    Middleware --> Controllers
    Controllers --> Mongo
    Controllers --> Cloudinary
    Controllers --> Resend
    Controllers --> Stream
    Controllers --> AI
    AI --> Groq
    AI --> Gemini

    Socket --> SocketAuth
    SocketAuth --> Mongo
    Socket -- "room lookup" --> Mongo
    Controllers -- "emit events" --> Socket
    Socket -- "live updates" --> Store
Loading

The frontend uses HTTP requests for normal app actions, such as logging in, loading chats, sending messages, uploading images, and creating groups. Protected routes check the JWT cookie, and most app routes also pass through Arcjet. Socket.IO is used for live updates, including new messages, online users, typing status, seen receipts, group updates, and call events.

How Messaging Works

  1. The user writes a message in the React app.
  2. The frontend sends the message to the backend with an HTTP request.
  3. The backend checks authentication and Arcjet protection.
  4. The backend saves the message in MongoDB.
  5. The backend emits a Socket.IO event to the other user or group room.
  6. Connected clients receive the event and update the chat UI.

Direct messages use:

POST /api/messages/send/:id

Group messages use:

POST /api/conversations/:id/messages

How the AI Bot Works

AI bots are stored as normal users in MongoDB. A bot user has isBot: true, plus a botProvider and botModel.

When a user sends a text message to a bot:

  1. The backend saves the user's message.
  2. The backend loads recent messages from that conversation.
  3. backend/lib/ai/index.js selects the bot's provider.
  4. The provider calls Groq or Gemini.
  5. The backend saves the bot reply.
  6. The backend sends the reply to the user with Socket.IO.

The app supports groq and gemini provider keys. The bot's provider is chosen from the bot user record. The code does not automatically switch providers if one fails.

How Real-Time Updates Work

The Socket.IO server lives in backend/lib/socket.js and shares the same Express app as the REST API, so both run on one port. Connections are authenticated by reading the same jwt cookie the REST API uses, in socket.auth.middleware.js.

Two delivery paths exist:

  • Direct messages and presence use an in-memory userSocketMap of userId -> socketId. Because this map lives in the server process, the app assumes a single backend instance.
  • Group messages use Socket.IO rooms named after the conversation id. On connect, the server joins the socket to every group the user belongs to, and the joinConversation event covers groups created while the user is already online.

Events the server emits: getOnlineUsers, newMessage, newGroupMessage, messagesSeen, userTyping, userStoppedTyping, addedToGroup, removedFromGroup, groupUpdated, incomingCall, callAnswered, callDeclined, callEnded, callFailed, and botReplyFailed.

Video calls use Socket.IO only for signalling. The call id is the two user ids sorted and joined with a hyphen, and the media session itself runs through Stream using a token from GET /api/stream/token.

Project Structure

chatle/
├── backend/
│   ├── lib/
│   │   ├── ai/                         # AI provider selection and prompts
│   │   ├── arcjet.js                   # Arcjet setup
│   │   ├── cloudinary.js               # Cloudinary upload setup
│   │   ├── db.js                       # MongoDB connection
│   │   ├── env.js                      # Environment variables
│   │   ├── resend.js                   # Email client
│   │   ├── socket.js                   # Socket.IO server
│   │   └── stream.js                   # Stream video client
│   ├── scripts/                        # Bot setup and maintenance scripts
│   └── src/
│       ├── controllers/                # Request handlers
│       ├── emails/                     # Email templates and send helpers
│       ├── middleware/                 # Auth, socket auth, and Arcjet middleware
│       ├── models/                     # Mongoose models
│       ├── routes/                     # API routes
│       └── server.js                   # Express server entry point
├── frontend/
│   ├── public/                         # Static files and demo assets
│   └── src/
│       ├── components/                 # React components
│       ├── i18n/                       # English and Khmer text
│       ├── lib/                        # Axios setup
│       ├── pages/                      # App pages
│       └── store/                      # Zustand stores
├── package.json
└── README.md

Getting Started

Requirements

  • Node.js 20 or newer
  • npm
  • MongoDB 6 or newer, either local or a MongoDB Atlas connection string

The app calls several external services, so signing up for these is part of local setup. Each one is free to start with:

  • Resend — required. Signup will not complete without a working verification email.
  • Cloudinary — required for avatar and image uploads.
  • Arcjet — required, because every app route passes through it.
  • Stream — needed for video calls only.
  • Groq or Gemini — needed for AI bot replies only.

See Environment Variables for the keys each one provides.

Install and Run

git clone https://github.com/SrunLyheang/chat.git chatle
cd chatle

Create backend/.env before starting the server, using the Environment Variables section below. The backend will not connect to MongoDB or sign auth cookies without it.

Start the backend:

cd backend
npm install
npm run dev

Start the frontend in another terminal:

cd frontend
npm install
npm run dev

The backend runs on:

http://localhost:3000

The frontend runs on:

http://localhost:5173

In development the frontend talks to http://localhost:3000/api directly, set in frontend/src/lib/axios.js, so both servers need to be running.

Optional: Seed an AI Bot

AI bots are ordinary user records, so one has to be created before you can chat with it:

cd backend
node scripts/seedBot.js
node scripts/seedBot.js groq-bot@yourapp.local "Groq Bot" groq llama-3.3-70b-versatile

The first command creates a Gemini-backed bot, and the second shows the argument order for creating a Groq-backed one.

Environment Variables

Create backend/.env.

PORT=3000
NODE_ENV=development
CLIENT_URL=http://localhost:5173

MONGO_URI=your_mongodb_connection_string
JWT_SECRET=your_jwt_secret

RESEND_API_KEY=your_resend_api_key
EMAIL_FROM=your_verified_sender_email
EMAIL_FROM_NAME=Chatle

CLOUDINARY_CLOUD_NAME=your_cloudinary_cloud_name
CLOUDINARY_API_KEY=your_cloudinary_api_key
CLOUDINARY_API_SECRET=your_cloudinary_api_secret

ARCJET_KEY=your_arcjet_key
ARCJET_ENV=development

STREAM_API_KEY=your_stream_api_key
STREAM_API_SECRET=your_stream_api_secret

GROQ_API_KEY=your_groq_api_key
GEMINI_API_KEY=your_gemini_api_key
BOT_USER_ID=optional_seeded_bot_user_id
Variable Purpose
PORT Backend port. Defaults to 3000.
NODE_ENV App environment. Use production for production builds.
CLIENT_URL Frontend URL allowed by CORS and Socket.IO.
MONGO_URI MongoDB connection string.
JWT_SECRET Secret used to sign JWT auth cookies.
RESEND_API_KEY Resend API key for email verification.
EMAIL_FROM Sender email address for Resend.
EMAIL_FROM_NAME Sender display name.
CLOUDINARY_CLOUD_NAME Cloudinary cloud name.
CLOUDINARY_API_KEY Cloudinary API key.
CLOUDINARY_API_SECRET Cloudinary API secret.
ARCJET_KEY Arcjet key for rate limiting and bot protection.
ARCJET_ENV Arcjet environment name.
STREAM_API_KEY Stream API key for video calls.
STREAM_API_SECRET Stream API secret for video calls.
GROQ_API_KEY Groq API key for AI bot replies.
GEMINI_API_KEY Gemini API key for AI bot replies.
BOT_USER_ID Optional ID used by bot scripts.

Do not commit real secrets. Use environment variables or a secret manager in production.

Deployment

This app is not currently deployed anywhere, and the repository contains no hosting configuration. What follows is how the code expects to be run in production, not a description of a live environment.

The project is built to deploy as a single service. When NODE_ENV is production, backend/src/server.js serves the built frontend from frontend/dist and falls back to index.html for any unmatched route, so the REST API, the Socket.IO server, and the static frontend all run on one port.

The scripts in the root package.json follow that shape:

npm run build   # installs backend and frontend deps, then builds the frontend
npm start       # runs the backend, which also serves frontend/dist

For a production run, set every variable from Environment Variables, with:

  • NODE_ENV=production, which makes the auth cookie secure and switches the Arcjet rate limiter from DRY_RUN to LIVE
  • CLIENT_URL set to the deployed origin, since it controls both the CORS allowlist and the Socket.IO origin check

Two constraints are worth knowing before choosing a host:

  • Single instance only. Online-user presence and direct-message routing are held in an in-memory map in the server process. Running more than one instance would split that state, so scaling out would need a shared adapter such as Redis first.
  • Cloudflare is assumed. backend/lib/arcjet.js is configured with proxies: [cloudflare()], meaning Arcjet reads the real client IP from Cloudflare's headers. Deploying without Cloudflare in front would make that client IP untrustworthy, so this setting should be changed to match whatever proxy actually sits in front of the app.

Security Notes

  • Protected REST routes use JWT authentication.
  • Socket.IO connections are authenticated with the JWT cookie.
  • Arcjet is used on most app API routes for rate limiting and bot protection.
  • Images are uploaded through the backend before being stored in Cloudinary.
  • Production deployments should use trusted CORS origins and secure secrets.

Troubleshooting

  • Socket.IO does not connect: Check CLIENT_URL, cookie settings, and withCredentials.
  • Emails do not arrive: Check Resend API key, sender setup, and recipient rules.
  • Images do not upload: Check all Cloudinary environment variables.
  • AI bot does not reply: Check the bot user record, provider key, model name, and API key.
  • Video calls do not work: Check STREAM_API_KEY and STREAM_API_SECRET.

About

Chatle is a full-stack real-time chat app where users add each other as friends, then exchange direct or group messages with images, replies, edits, typing indicators, seen receipts, and one-to-one video calls — plus AI bot users you can chat with like any other contact.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages