A full-stack real-time chat application with support for private and group conversations.
✅ User Authentication (Register/Login with JWT)
✅ Private Chat (1-on-1)
✅ Group Chat (Multiple participants)
✅ Real-time Messaging via Socket.IO
✅ Typing Indicators
✅ Online User Status
✅ Message History
✅ Modern Responsive UI
- Node.js + Express
- MongoDB + Mongoose
- Socket.IO (WebSocket)
- JWT Authentication
- Cloudinary (Image uploads)
- Multer (File Upload)
- React 19 + TypeScript
- Redux Toolkit (State management)
- Socket.IO Client
- Tailwind CSS
- Vite
- Node.js 18+
- MongoDB (local or Atlas)
- npm or yarn
git clone https://github.com/098ff/waffle-chat.git
cd waffle-chatcd backend
npm installcp config/config.env.example config/config.env
Edit config/config.env with your MongoDB URI, JWT_SECRET, etc.
npm run dev
Backend will run on http://localhost:5000
cd ../frontend
npm installcp .env.example .env
Edit .env if needed (default: http://localhost:5000)
npm run dev
Frontend will run on http://localhost:5173
- Register - Create a new account at
/register - Login - Sign in at
/login - Create Chat - Click ➕ to create a private or group chat
- Send Messages - Select a chat and start messaging in real-time
POST /api/auth/register- Register new userPOST /api/auth/login- Login userGET /api/auth/check- Check auth status
POST /api/chats- Create chat (private/group)GET /api/chats- Get user's chatsGET /api/chats/:id/messages- Get chat messagesPOST /api/chats/:id/messages- Send message (REST fallback)
GET /api/messages/contacts- Get all contactsGET /api/messages/chats- Get chat partnersGET /api/messages/:userId- Get messages with user
join-room- Join chat roomleave-room- Leave chat roommessage:create- Send messagetyping- Typing indicator
message:new- New message receivedtyping- User typinggetOnlineUsers- Online users listmember:joined- Member joined roommember:left- Member left room
waffle-chat/
├── backend/
│ ├── config/ # DB, Socket, Cloudinary config
│ ├── controllers/ # Route handlers
│ ├── middleware/ # Auth middleware
│ ├── models/ # Mongoose schemas
│ ├── routes/ # API routes
│ └── server.js # Entry point
├── frontend/
│ └── src/
│ ├── pages/ # React pages
│ ├── services/ # API & Socket services
│ ├── store/ # Redux store
│ ├── types/ # TypeScript types
│ └── App.tsx # Main app
└── README.md
Test backend with the provided socket test script:
node backend/tests/test-socket.js http://localhost:5000 <JWT_TOKEN> <CHAT_ID>Or use Postman/curl for REST API testing.
cd frontend
npm run build # Check for TypeScript errorsThis application is configured for deployment on AWS Elastic Beanstalk with Docker Compose.
- AWS Account
- EB CLI installed (
pip install awsebcli) - Docker and Docker Compose configured
- Initialize Elastic Beanstalk
eb init -p docker waffle-chat --region ap-southeast-2- Create Environment with Load Balancer
eb create waffle-chat-env --instance-type t2.small --elb-type application- Configure Environment Variables
eb setenv \
PORT=5000 \
MONGO_URI="your-mongodb-uri" \
NODE_ENV=production \
JWT_SECRET="your-secret" \
CLIENT_URL="http://your-domain.com" \
CLOUDINARY_CLOUD_NAME="your-cloud-name" \
CLOUDINARY_API_KEY="your-api-key" \
CLOUDINARY_API_SECRET="your-api-secret"-
Enable Sticky Sessions (REQUIRED for Socket.IO)
- Go to AWS Console → Elastic Beanstalk → Configuration → Load Balancer
- Edit default process
- Enable Stickiness →
Load balancer generated cookie - Set Cookie duration:
86400(1 day) - Save and apply
-
Deploy Application
# Create deployment package
zip -r deploy.zip . -x "*.git*" "*node_modules/*" "*.env" "*.zip"
# Deploy
eb deploy- Configure HTTPS (Required for microphone access)
- Request SSL certificate in AWS Certificate Manager
- Add HTTPS listener on port 443 in Load Balancer configuration
- Enable HTTP → HTTPS redirect
Symptoms: Image previews not showing, inline styles blocked, WebSocket connections failing
Solution: Update nginx.conf to include proper CSP directives:
# Add blob: to img-src for image previews
set $CSP_image "img-src 'self' data: blob: https://res.cloudinary.com";
# Add media-src for audio/video
set $CSP_media "media-src 'self' blob: data: https://res.cloudinary.com";
# Include in final CSP
set $CSP "default-src 'self'; ${CSP_script}; ${CSP_style}; ${CSP_image}; ${CSP_media}; ${CSP_connect}; ${CSP_font}; ${CSP_frame}; ${CSP_object}; ${CSP_base}";Symptoms: 500 errors, WebSocket upgrade fails, real-time features not working
Root Cause: Without sticky sessions, each request hits a different instance. Socket.IO's multi-step handshake breaks because:
- Step 1 (polling): Goes to Instance A → Creates session
- Step 2 (WebSocket upgrade): Goes to Instance B → Doesn't know about session → FAILS
Solution: ALWAYS enable sticky sessions (see step 4 above)
Why it works: Sticky sessions use cookies (AWSALB) to route all requests from same user to same instance, maintaining Socket.IO connection state.
Symptoms: Command execution completed on all instances. Summary: [Successful: 0, TimedOut: 1]
Solutions:
- Increase timeout:
eb deploy --timeout 20 - Terminate stuck instance via EC2 Console (load balancer auto-creates new one)
- Restart environment:
eb restart - If persistent, create new environment with clean state
Symptoms: ERROR: failed to solve: dockerfile parse error: unknown instruction: pax_global_header
Cause: Archive created with tar before zip (double-archiving)
Solution: Create ZIP directly without tar:
zip -r deploy.zip . -x "*.git*" "*node_modules/*" "*.env"Never do: tar -czf project.tar.gz . && zip deploy.zip project.tar.gz
Symptoms: Session Manager shows "SSM Agent is not online"
Solutions:
- Use AWS Console → EC2 → Instance Connect (alternative to SSM)
- Add EC2 key pair to environment:
- Configuration → Security → Edit → Select EC2 key pair
- Apply (will recreate instance)
- For logs without SSH:
eb logs --all
Symptoms: The "CLIENT_URL" variable is not set. Defaulting to a blank string
Solution: Set via EB CLI or Console:
eb setenv CLIENT_URL=https://your-domain.comSymptoms: Browser blocks microphone access with "Not allowed" error
Cause: Modern browsers require HTTPS for microphone/camera access
Solution: Configure HTTPS with AWS Certificate Manager (see step 6 above)
Symptoms: Environment goes to "Red" status after upload new deploy and re-deployment
Cause: Insufficient role permission for Elastic Beanstalk to pull public Docker images from Docker Hub
Solution: Add roles permission in IAM for AWS Service: elasticbeanstalk (AmazonEC2ContainerRegistryPullOnly) to pull public Docker images
- nginx: Serves frontend static files, proxies API and Socket.IO to backend
- backend: Node.js/Express container on port 5000
- Load Balancer: Routes traffic with sticky sessions for Socket.IO
- MongoDB: External (MongoDB Atlas recommended)
- Cloudinary: External for image/audio storage
Load balancer checks path / on port 80 (nginx). For better monitoring, add health endpoint to backend:
// backend/server.js
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok' });
});Then configure load balancer health check path to /api/health.
# Check environment status
eb health
# View logs
eb logs --stream
# Check running containers
eb ssh
sudo docker ps
sudo docker logs waffle-chat-backend- Set environment variables
- Ensure MongoDB is accessible
- Run
npm startor use PM2
- Build:
npm run build - Serve dist/ folder with nginx/vercel/netlify
- Fork the repository
- Create feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open Pull Request
MIT