A real-time messaging application built with a full-stack MERN architecture, Firebase Authentication, and Socket.io for live communication. Features a premium dark/light UI with instant messaging, online presence, emoji support, and avatar customization.
- Features
- Tech Stack
- Architecture
- Project Structure
- Prerequisites
- Local Setup
- Available Scripts
- Environment Variables
- API Reference
- Deployment
- Known Limitations
- Authentication — Email/password registration and login via Firebase Auth
- Avatar picker — 9 randomly generated avatars (avataaars, bottts, pixel-art) via DiceBear API v9
- Real-time messaging — Instant message delivery via Socket.io (no page refresh needed)
- Online presence — Live online/offline status indicators for all users
- Message persistence — Full chat history stored in MongoDB, loaded on room open
- Emoji picker — Integrated emoji selector in the chat input
- Search — Search users and existing conversations
- Dark / Light mode — Toggle between themes, preference persisted in localStorage
- Responsive layout — Sidebar + chat panel layout, works on desktop browsers
| Package | Version | Purpose |
|---|---|---|
| React | 18 | UI rendering and component state |
| TailwindCSS | 3 | Utility-first styling, dark/light mode |
| React Router DOM | 6 | Client-side routing |
| Socket.io Client | 4 | Real-time WebSocket connection |
| Axios | 0.27 | HTTP requests to the backend API |
| Firebase JS SDK | 9 | Client-side auth (register, login, logout) |
| emoji-picker-react | 3 | Emoji selector component |
| timeago.js | 4 | Human-readable message timestamps |
| @heroicons/react | 1 | Icon library |
| @headlessui/react | 1 | Accessible modal components |
| Package | Version | Purpose |
|---|---|---|
| Node.js | 18+ | JavaScript runtime |
| Express | 4 | REST API server |
| Socket.io | 4 | Real-time event handling and user presence |
| Mongoose | 6 | MongoDB ODM and schema definitions |
| Firebase Admin SDK | 11 | Server-side JWT token verification |
| dotenv | 16 | Environment variable loading |
| cors | 2 | Cross-origin request handling |
| Service | Purpose |
|---|---|
| MongoDB | Persistent storage for chat rooms and messages |
| Firebase Authentication | User identity, token issuance |
User registers/logs in (Firebase client SDK)
→ Firebase issues an ID token (JWT)
→ Token sent in Authorization header on every API request
→ VerifyToken middleware (Firebase Admin SDK) validates it
→ Socket.io handshake also carries the token (VerifySocketToken)
Sender types message → emits "sendMessage" via Socket.io
→ Server looks up receiver's socket ID in the onlineUsers Map
→ Emits "getMessage" directly to receiver's socket
→ Message simultaneously saved to MongoDB via REST API
→ Receiver sees message instantly without polling
ChatRoom
members: [uid, uid] // Firebase UIDs of the two participants
createdAt / updatedAt
ChatMessage
chatRoomId: ObjectId // reference to ChatRoom
sender: uid // Firebase UID of sender
message: String
createdAt / updatedAt
Users are not stored in MongoDB. All user data (uid, displayName, photoURL, email) is managed by Firebase and retrieved via the Firebase Admin SDK on demand.
Wavr/
├── server/
│ ├── config/
│ │ ├── firebase-config.js # Firebase Admin SDK init (env var or local JSON)
│ │ └── mongo.js # Mongoose connection with reconnect handling
│ ├── controllers/ # Business logic for users, rooms, messages
│ ├── middlewares/
│ │ └── VerifyToken.js # JWT verification for HTTP and Socket.io
│ ├── models/
│ │ ├── ChatRoom.js
│ │ └── ChatMessage.js
│ ├── routes/
│ │ ├── user.js
│ │ ├── chatRoom.js
│ │ └── chatMessage.js
│ └── index.js # Express + Socket.io server entry point
├── frontend/
│ └── src/
│ ├── components/
│ │ ├── accounts/ # Login, Register, Profile, Logout
│ │ ├── chat/ # ChatRoom, Message, ChatForm, AllUsers, Contact, SearchUsers, Welcome
│ │ └── layouts/ # Header, ChatLayout, UserLayout, ThemeToggler, ErrorMessage
│ ├── contexts/
│ │ └── AuthContext.js # Global auth state via React Context
│ ├── services/
│ │ └── ChatService.js # All API calls (Axios) and Socket.io connection
│ ├── config/
│ │ └── firebase.js # Firebase client SDK init
│ └── utils/
│ ├── GenerateAvatar.js # DiceBear API v9 avatar URL generator
│ └── WithPrivateRoute.js
├── .env.example # Server environment variable template
├── package.json # Server dependencies and npm scripts
└── README.md
- Node.js v18 or higher
- MongoDB running locally (or a MongoDB Atlas URI)
- Firebase project with Email/Password authentication enabled
- nodemon installed globally for local development:
npm install -g nodemon
# Clone the repository
git clone https://github.com/YOUR_USERNAME/wavr.git
cd wavr
# Install server dependencies (run from root)
npm install
# Install frontend dependencies
cd frontend && npm install- Go to Firebase Console → create or select a project
- Enable Email/Password sign-in: Authentication → Sign-in method → Email/Password → Enable
- Generate a service account key: Project Settings → Service Accounts → Generate new private key
- Save the downloaded file as
server/config/serviceAccountKey.json
Server — create .env in the root directory:
PORT=8080
MONGO_URI=mongodb://127.0.0.1:27017/chat_app
CLIENT_URL=http://localhost:3000Frontend — create .env in the frontend/ directory:
REACT_APP_FIREBASE_API_KEY=your_api_key
REACT_APP_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com
REACT_APP_FIREBASE_PROJECT_ID=your_project_id
REACT_APP_FIREBASE_STORAGE_BUCKET=your_project.firebasestorage.app
REACT_APP_FIREBASE_MESSAGING_SENDER_ID=your_sender_id
REACT_APP_FIREBASE_APP_ID=your_app_idAll values are found in Firebase Console → Project Settings → General → Your apps → SDK setup.
# Windows — verify the service is running
Get-Service -Name MongoDB
# Start it if stopped
Start-Service -Name MongoDBOpen two terminals:
# Terminal 1 — backend (from root, with auto-restart)
npm run dev
# Expected: "Server listening on port 8080" + "Mongo has connected successfully"
# Terminal 2 — frontend (from frontend/)
npm start
# Expected: "Compiled successfully!"| Script | Command | Description |
|---|---|---|
npm run dev |
nodemon server/index.js |
Start server locally with auto-restart on file changes |
npm start |
node server/index.js |
Start server for production (no auto-restart) |
npm run devis for local development only.npm startis what production hosts (e.g. Render) use — it runsnodedirectly without nodemon, which is not available on remote servers unless explicitly installed.
| Script | Command | Description |
|---|---|---|
npm start |
react-scripts start |
Start React dev server with hot reloading |
npm run build |
react-scripts build |
Build optimised production bundle to build/ |
npm test |
react-scripts test |
Run tests with Jest + React Testing Library |
| Variable | Required | Description |
|---|---|---|
PORT |
No | Server port (default: 8080) |
MONGO_URI |
Yes | MongoDB connection string |
CLIENT_URL |
Yes | Frontend URL for Socket.io CORS |
FIREBASE_SERVICE_ACCOUNT |
Production only | Full serviceAccountKey.json content as a JSON string |
| Variable | Required | Description |
|---|---|---|
REACT_APP_FIREBASE_API_KEY |
Yes | Firebase web API key |
REACT_APP_FIREBASE_AUTH_DOMAIN |
Yes | Firebase auth domain |
REACT_APP_FIREBASE_PROJECT_ID |
Yes | Firebase project ID |
REACT_APP_FIREBASE_STORAGE_BUCKET |
Yes | Firebase storage bucket |
REACT_APP_FIREBASE_MESSAGING_SENDER_ID |
Yes | Firebase messaging sender ID |
REACT_APP_FIREBASE_APP_ID |
Yes | Firebase app ID |
REACT_APP_BACKEND_URL |
Production only | Deployed backend URL (e.g. https://wavr-backend.onrender.com) |
All endpoints require a valid Firebase ID token:
Authorization: Bearer <firebase_id_token>
| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Fetch all users (max 10) |
GET |
/:userId |
Fetch a single user by Firebase UID |
| Method | Endpoint | Description |
|---|---|---|
POST |
/ |
Create a new chat room between two users |
GET |
/:userId |
Get all chat rooms for a user |
GET |
/:firstUserId/:secondUserId |
Get room between two specific users |
| Method | Endpoint | Description |
|---|---|---|
GET |
/:roomId |
Fetch all messages in a chat room |
POST |
/ |
Save a new message |
| Event | Direction | Payload | Description |
|---|---|---|---|
addUser |
Client → Server | userId |
Register user as online |
getUsers |
Server → Client | [[userId, socketId], ...] |
Broadcast online users list |
sendMessage |
Client → Server | { senderId, receiverId, message } |
Send a message |
getMessage |
Server → Client | { senderId, message } |
Receive a message |
Wavr can be deployed entirely on free tiers using:
| Layer | Service |
|---|---|
| Frontend | Vercel |
| Backend | Render |
| Database | MongoDB Atlas (M0 free cluster) |
- MongoDB Atlas — create a free M0 cluster, whitelist all IPs (
0.0.0.0/0), get the connection string - Render — deploy from GitHub, set root directory to
Wavr/, build commandnpm install, start commandnpm start(runsnode server/index.jsdirectly — nodemon is not used in production), add all server env vars includingFIREBASE_SERVICE_ACCOUNT - Vercel — deploy from GitHub, set root directory to
Wavr/frontend, add allREACT_APP_*env vars plusREACT_APP_BACKEND_URLpointing to your Render URL - Firebase Console — add your Vercel domain to Authentication → Settings → Authorized domains
- Render — update
CLIENT_URLto your Vercel URL and redeploy
Note: Render's free tier spins down after 15 minutes of inactivity. The first request after a period of no traffic will take ~30 seconds (cold start). This is a trade-off of the free tier.
- Local only by default — without deployment, only the machine running the server can use the app
- No media sharing — text and emoji messages only; no image or file uploads
- One-on-one only — no group chat support; rooms are strictly between two users
- Max 10 users listed — the
/api/userendpoint returns a maximum of 10 users - Render cold starts — deployed backend on the free tier takes ~30s to wake after inactivity