| Feature | Description |
|---|---|
| 🔐 JWT Authentication | Signup/login with HTTP-only cookie sessions — no third-party auth |
| ⚡ Real-Time Messaging | Instant bidirectional messaging via Socket.io with optimistic UI updates |
| 😄 Emoji Reactions | React to any message; reactions sync live across both clients |
| 🗑️ Message Deletion | Soft-delete your own messages; deleted state propagates in real time |
| ✍️ Typing Indicators | Animated dots when the other person is typing, debounced at 1.5s |
| 🔴 Unread Badges | Per-conversation unread counts, cleared automatically on open |
| 🟢 Online Presence | Live online/offline status tracked per socket connection |
| 📸 Image Sharing | Upload and send images, stored via Cloudinary CDN |
| 🔔 Sound Effects | Notification and keystroke audio with a per-session toggle |
| 📧 Welcome Emails | Transactional email on account creation via Resend |
| 🚦 Rate Limiting | API abuse prevention powered by Arcjet |
| 📱 Responsive UI | Works across desktop and mobile screen sizes |
Frontend
- React.js — UI library
- Zustand — lightweight global state management
- Tailwind CSS + DaisyUI — styling
- Socket.io-client — real-time events
- Axios — HTTP client
- Vite — build tool
Backend
- Node.js + Express.js — REST API server
- MongoDB + Mongoose — database & ODM
- Socket.io — WebSocket server
- bcryptjs — password hashing
- jsonwebtoken — JWT auth
- Cloudinary — image storage & CDN
- Resend — transactional email
- Arcjet — rate limiting & bot protection
- Node.js
>=20.0.0 - MongoDB Atlas account (or local MongoDB)
- Cloudinary account
- Resend account
- Arcjet account
git clone https://github.com/adityack477/yapa.git
cd yapaCreate a .env file inside backend/:
PORT=3000
MONGO_URI=your_mongodb_connection_string
NODE_ENV=development
JWT_SECRET=your_long_random_secret_here
# Resend — transactional email
RESEND_API_KEY=re_xxxxxxxxxxxx
EMAIL_FROM=hello@yourdomain.com
EMAIL_FROM_NAME=Yapa
# Frontend URL (for CORS and email links)
CLIENT_URL=http://localhost:5173
# Cloudinary — image uploads
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
# Arcjet — rate limiting
ARCJET_KEY=ajkey_xxxxxxxxxxxx
ARCJET_ENV=developmentcd backend
npm install
npm run devServer starts at http://localhost:3000
# in a new terminal, from the project root
cd frontend
npm install
npm run devApp runs at http://localhost:5173
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/signup |
Register a new account | ❌ |
POST |
/login |
Login and receive JWT cookie | ❌ |
POST |
/logout |
Clear session cookie | ✅ |
GET |
/check |
Verify current session | ✅ |
PUT |
/update-profile |
Update profile picture | ✅ |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/contacts |
Get all users (excluding self) | ✅ |
GET |
/chats |
Get users you've chatted with | ✅ |
GET |
/unread/counts |
Get unread message counts per user | ✅ |
GET |
/:id |
Fetch message history with a user | ✅ |
POST |
/send/:id |
Send a message to a user | ✅ |
DELETE |
/:id |
Soft-delete your own message | ✅ |
POST |
/:id/react |
Add or toggle an emoji reaction | ✅ |
| Event | Direction | Payload | Description |
|---|---|---|---|
newMessage |
server → client | Message |
New incoming message |
messageDeleted |
server → client | { messageId } |
Message was deleted |
reactionUpdated |
server → client | { messageId, reactions } |
Reactions changed |
getOnlineUsers |
server → client | string[] |
Updated online user list |
typingStart |
client → server | { receiverId } |
User started typing |
typingStop |
client → server | { receiverId } |
User stopped typing |
userTyping |
server → client | { senderId } |
Someone is typing to you |
userStoppedTyping |
server → client | { senderId } |
They stopped typing |
yapa/
├── backend/
│ ├── src/
│ │ ├── controllers/
│ │ │ ├── auth.controller.js # signup, login, logout, updateProfile
│ │ │ └── message.controller.js # messages, reactions, delete, unread
│ │ ├── middleware/
│ │ │ ├── auth.middleware.js # JWT verification (REST)
│ │ │ ├── socket.auth.middleware.js # JWT verification (WebSocket)
│ │ │ └── arcjet.middleware.js # rate limiting
│ │ ├── models/
│ │ │ ├── User.js # user schema
│ │ │ └── Message.js # message schema (reactions, soft-delete)
│ │ ├── routes/
│ │ │ ├── auth.route.js
│ │ │ └── message.route.js
│ │ └── lib/
│ │ ├── socket.js # Socket.io server + typing events
│ │ ├── db.js # MongoDB connection
│ │ ├── cloudinary.js
│ │ ├── resend.js
│ │ └── env.js # validated env vars
│ ├── .env.example
│ └── package.json
│
└── frontend/
└── src/
├── components/
│ ├── MessageBubble.jsx # reactions + delete UI
│ ├── MessageInput.jsx # typing emit + image upload
│ ├── ChatContainer.jsx
│ ├── ChatHeader.jsx # typing indicator + online status
│ ├── ChatsList.jsx # sidebar chats + unread badges
│ └── ContactList.jsx
├── pages/
│ ├── ChatPage.jsx
│ ├── LoginPage.jsx
│ └── SignUpPage.jsx
├── store/
│ ├── useChatStore.js # messages, reactions, typing, unread
│ └── useAuthStore.js # auth + socket connection
├── hooks/
│ └── useKeyboardSound.js
└── lib/
└── axios.js
Optimistic UI — Messages appear in the UI immediately with a temp ID before the server responds. On success the placeholder is swapped with the real document; on failure it's rolled back.
Soft Delete — Deleted messages set deletedAt and clear text/image fields in MongoDB rather than hard removal. The receiver sees a "Message deleted" placeholder. The socket event messageDeleted propagates the state change in real time.
Reaction Toggle — A user can only hold one reaction per message at a time. Sending the same emoji again removes it. The full reactions array is broadcast to both participants via reactionUpdated.
Typing Debounce — typingStart is emitted once at the start of a burst of keystrokes. typingStop fires 1500ms after the last keystroke via a debounced timeout, preventing excessive socket events.
Unread Counts — Computed server-side using a MongoDB aggregation pipeline that groups unread messages (readAt: null) by senderId. Opening a conversation stamps readAt via updateMany and clears the badge locally in Zustand.
| Service | Platform |
|---|---|
| Frontend | Vercel |
| Backend | Render |
| Database | MongoDB Atlas |
Set Root Directory → backend, Build Command → npm install, Start Command → npm start.
Add these environment variables in the Render dashboard:
| Variable | Value |
|---|---|
NODE_ENV |
production |
PORT |
3000 |
MONGO_URI |
MongoDB Atlas connection string |
JWT_SECRET |
Long random secret |
CLIENT_URL |
Your Vercel frontend URL |
RESEND_API_KEY |
From resend.com |
EMAIL_FROM |
Your verified sender address |
EMAIL_FROM_NAME |
Yapa |
CLOUDINARY_CLOUD_NAME |
From Cloudinary dashboard |
CLOUDINARY_API_KEY |
From Cloudinary dashboard |
CLOUDINARY_API_SECRET |
From Cloudinary dashboard |
ARCJET_KEY |
From arcjet.com |
ARCJET_ENV |
production |
Add this environment variable in the Vercel dashboard:
| Variable | Value |
|---|---|
VITE_API_URL |
Your Render backend URL (no trailing slash) |
Important:
CLIENT_URLon Render must exactly match your Vercel URL, andVITE_API_URLmust point to your Render URL — these two are what connect the frontend and backend.
MIT © Aditya Kadam
