A robust, real-time group chatroom with audio and image upload, built with React, TypeScript, Vite, shadcn-ui, and Tailwind CSS. All users see the same chat messages and user list in real time. Audio and image files are uploaded and shared instantly with modern playback and preview features.
- One Dockerfile (
Dockerfile.allinone) builds and runs both the Node app and MongoDB in a single container. - Zero platform-specific configuration required: no
MONGO_URIneeded;server.jsdefaults tomongodb://127.0.0.1:27017/chatappinside the container. - Health endpoint at
/healthreports{ status, mongo }wheremongoisconnected|connecting|disconnected. - Logs show both processes under Supervisor: look for
mongod ... Waiting for connectionsandConnected to MongoDBfrom the app.
- Real-time chat with instant message delivery
- Audio file upload and streaming playback in chat (with metadata, cover art, and progress bar)
- Custom avatar upload and management for each user
- Image sharing in chat messages with click-to-enlarge modal
- Rich link previews for URLs shared in chat (supports most websites including YouTube etc.)
- User list with avatars and online status
- Audio notification settings (enable/disable sounds)
- Responsive UI with shadcn-ui and Tailwind CSS
- All state synchronized via backend events (no local-only state)
- Deep logging and error handling
The chat automatically detects and displays rich previews for URLs:
- Automatic detection of URLs in messages
- Displays website title, description, and thumbnail image
- Supports most major websites including YouTube, Twitter, news sites, and more
- Click the preview to visit the original page
- Preview updates in real-time for all users
The chat supports audio and image sharing with the following features:
Audio Player Features:
- 🎵 Direct Streaming Playback: Audio files stream directly without full download
- 🎨 Rich Metadata Display: Shows artist, title, and album from audio files
- 🖼️ Cover Art: Displays embedded album art when available
- ⏯️ Playback Controls: Play/pause, volume control, and seek functionality
- ⏱️ Time Tracking: Current position and duration display
- 📈 Waveform Visualization: Visual representation of audio waveform
- 🔄 Live Streaming: Supports live audio streams with adaptive bitrate
- 📱 Mobile Optimized: Touch-friendly controls for all devices
- 📊 Real Upload Progress: Visual progress bar shows actual upload status for audio files (0-100%)
General Media Features:
- Click the audio or image icon in the chat input to select a file
- Audio files are uploaded directly (no base64 conversion) with streaming playback
- Smooth audio playback with progress bar and time tracking
- Cover art and metadata are extracted and displayed with audio messages
- Images are automatically resized and optimized
- Maximum file size: 10MB for audio, 5MB for images
- Supported formats: All standard image and audio formats (JPEG, PNG, GIF, MP3, WAV, OGG, etc.)
- Images and audio are displayed inline with chat messages
- Upload a custom avatar (automatically resized and compressed)
- Avatar updates are synced across all connected clients
- Supports JPG, PNG, and WebP formats
- Toggle sound effects for new messages
- Notification sounds play when receiving new messages (when chat is not focused)
- Volume control for notification sounds
- Click any image in chat to open it in a full-screen modal
- Zoom and pan high-resolution images
- Click outside or press Escape to close
- Frontend: React + TypeScript, modular components for chat, user management, avatars, input, audio player, and upload logic
- Backend: Node.js server (see
server.js) manages users, messages, uploads, and avatar data in memory and filesystem - Communication: WebSockets for real-time updates; REST endpoints for file uploads
-
The backend always runs on port 3000 (required for Coolify and production).
-
You MUST create a
.envfile in the project root before running the app locally. -
The
.envfile is NOT tracked by git and must be created by each developer. -
Add this line to your
.envfile:VITE_SOCKET_URL=http://localhost:3000 -
If
.envis missing or blank, the frontend will NOT connect to the backend. You will see the interface but no users or messages. -
If you ever see the UI but no chat/users, check your
.envfirst!
Issue: The app was connecting to ws://localhost:3000 in production instead of the production domain, causing WebSocket connection failures.
Root Cause: The local development environment variable VITE_SOCKET_URL=http://localhost:3000 was being picked up by the production build process (Coolify), resulting in hardcoded localhost URLs in the production bundle.
Solution: Modified the Socket.IO client logic in src/components/Chatroom.tsx to intelligently handle localhost URLs:
// Smart URL detection for dev/prod
if (import.meta.env.VITE_SOCKET_URL && !import.meta.env.VITE_SOCKET_URL.includes('localhost')) {
// Use VITE_SOCKET_URL only if it's not localhost
url = import.meta.env.VITE_SOCKET_URL;
} else if (import.meta.env.DEV) {
// Development mode - use localhost
url = 'http://localhost:3000';
} else {
// Production mode - auto-detect current domain
url = `${window.location.protocol}//${window.location.hostname}`;
}Result:
- ✅ Local development still uses
localhost:3000 - ✅ Production automatically detects and uses the current domain (e.g.,
https://chat.supersoul.top) - ✅ No environment variable changes needed in deployment platforms
- ✅ Backward compatible with existing setups
Prevention: This fix ensures the app will always work correctly regardless of what VITE_SOCKET_URL is set to in the build environment.
To ensure the servers start correctly and avoid port conflicts/zombie processes, use the automated startup script. This is the only recommended way to start the local development environment.
./start-dev.shWhat this script does for you:
- 🔍 Pre-flight Check: Automatically detects and force-kills any zombie processes on ports 3000 and 5173.
- 📝 Environment Sync: Ensures your
.envfile exists (copies from.env.exampleif missing). - 🚀 Parallel Startup: Starts the backend database connection and the frontend development server.
- 🏥 Health Verification: Periodically pings the backend
/healthendpoint until it's ready before letting you know it's "All Systems Go".
- Ensure
.envexists. Minimum:VITE_SOCKET_URL=http://localhost:3000(for split dev)MONGO_URI=mongodb://localhost:27017/chatapp(if running Mongo locally)
- Start MongoDB (local via Docker):
docker compose up -d mongodb - Start backend:
node server.js(port 3000) - Start frontend (dev):
npm run dev(port 5173) - If using same-origin in production (Coolify), you can omit
VITE_SOCKET_URL. - Troubleshoot ports:
./check-servers.shand seestartup-server-guide.md.
- Frontend (Vite dev server): usually http://localhost:5173 (Vite may pick 5174/5175 if busy; check terminal output)
- Backend (server.js): http://localhost:3000
- Frontend → Backend socket URL: configured via
.envVITE_SOCKET_URL(dev default:http://localhost:3000) - Production (Coolify/Docker): backend serves static frontend and Socket.IO on port 3000 behind your domain
- Online users are computed from in-memory connections only (no DB reads for presence)
- On join/disconnect, server emits
usersfrom memory - Client emits
leaveon tab close (beforeunload) for immediate removal - Server
pingTimeoutis 15s to reduce linger on abrupt closes - Database persists messages, media, avatars, and last-seen/status for history only
- Start backend:
node server.js(http://localhost:3000) - Start frontend:
npm run dev(visit the printed Vite URL) - Open two tabs, join with two usernames
- Close one tab → the closed user should disappear immediately in the other tab
- If a tab is force-closed and lingers, it will clear within ~15s
- “Username already taken” right after a crash/force-close: wait ~15s or refresh; the server will drop stale presence
- If UI shows but no chat/users: verify
.envcontainsVITE_SOCKET_URL=http://localhost:3000in dev
- Local Dev Guide:
LOCAL_DEV_SETUP.md - MongoDB Integration & TTL:
mongodb-setup.md - Startup/Ports/Checks:
startup-server-guide.md - Docker (local):
docker-compose.yml - Docker (Coolify deploy):
docker-compose.coolify.yml - Architecture & Roadmap:
plan.md - Audio flow and known issues:
audio-feature.md,live-audio-upload-errors.md
- OSSPlayer (Azuracast/Icecast corner player) in
src/components/OSSPlayer.tsx:- Implemented EST schedule-based switching via
SHOW_WINDOWS. - On station change, updates
<audio>src, callsload(), and auto-resumesplay()if it was already playing. - Countdown shows time until next live start and “Live Now!” during the window.
- Error fallback: if live errors/stalls, falls back to main and suppresses live retry for 2 minutes to avoid flapping.
- Chat message audio player is unchanged.
- Implemented EST schedule-based switching via
- Code:
src/components/OSSPlayer.tsx - Streams:
- Main:
https://supersoul.site:8000/OSS-320 - Live:
https://supersoul.site:8010/OSSlive
- Main:
- Metadata API:
https://supersoul.site/api/nowplaying(station id: main=1, live=15) - Current live schedule (EST):
- Saturday 20:00–23:59
- Sunday 00:00–01:00
- Configured via
SHOW_WINDOWSin code.
- Behavior:
- Auto-switches between main/live by schedule; updates the actual audio stream, not just the label.
- Auto-resumes playback after switch if it was playing.
- If the live stream fails, falls back to main and retries after ~2 minutes.
- Start the player and confirm audio.
- Temporarily add a
SHOW_WINDOWSwindow that includes the current time to simulate “Live Now!” - Verify the Network tab shows the
<audio>request switch from:8000/OSS-320to:8010/OSSlive. - Revert the temporary window.
- Confirm exact EST live windows and station IDs; update
SHOW_WINDOWSif needed. - Manually test around a real boundary or simulate as above.
- Optional (later): minimal admin panel to edit schedule windows; simple password auth; store in MongoDB.
- Node.js & npm installed (install with nvm)
# Clone the repository
git clone https://github.com/Catskill909/chatroom.git
# Navigate to the project directory
cd chatroom
# Install dependencies
npm install
# Build the frontend
npm run build
# Start the backend server (serves both backend and built frontend)
node server.js
# Alternatively, for separate dev frontend:
npm run dev- Open your browser at
http://localhost:3000(production build) or the port shown in the terminal for dev.
- Vite
- TypeScript
- React
- shadcn-ui
- Tailwind CSS
- Node.js (backend)
This app is fully production-ready and tested at https://chat.supersoul.top.
Recommended: Use the provided multi-stage Dockerfile for deployment.
# Stage 1: Build
FROM node:20-bookworm AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:20-bookworm
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev && apt-get update && apt-get upgrade -y && apt-get clean && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/server.js ./
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]- Exposes port 3000 (default, see
server.js). - Installs all build tools only in the build stage, keeping the runtime image small and secure.
- Works with Coolify, Docker, and most modern PaaS.
PORT(default: 3000)SSL_KEY_PATHandSSL_CERT_PATHfor HTTPS (see below)VITE_SOCKET_URLfor frontend-to-backend WebSocket URL (set tohttps://chat.supersoul.topin production)MONGO_URIMongoDB connection string (e.g.mongodb://localhost:27017/chatapplocally, ormongodb://mongodb:27017/chatappinside Docker/Coolify)
- To enable HTTPS, set:
SSL_KEY_PATH— Path to your SSL private key file (e.g.,/etc/letsencrypt/live/yourdomain.com/privkey.pem)SSL_CERT_PATH— Path to your SSL certificate file (e.g.,/etc/letsencrypt/live/yourdomain.com/fullchain.pem)
- The server will automatically use HTTPS if both variables are set.
- If deploying frontend and backend on different origins, set
VITE_SOCKET_URLin your frontend environment to the full wss:// or https:// URL of your backend (e.g.,wss://chat.supersoul.top).
Use the provided docker-compose.coolify.yml for GitOps-style deployments that preserve data across updates.
Steps:
- In Coolify: New Resource → Docker Compose → Git Repository.
- Select this repo/branch and set Compose Path to
docker-compose.coolify.yml. - Enable Auto Deploy on push (optional).
- Add a Domain to the
appservice. Coolify will proxy to internal port 3000 automatically. - Issue a TLS certificate in Coolify (Let's Encrypt) for HTTPS/WSS.
What this Compose sets up:
- Service
mongodbwith a named volumemongodb_datafor persistent DB. - Service
app(built from Dockerfile) with a named volumeuploadsmounted at/app/uploadsso media persists. - Environment
MONGO_URI=mongodb://mongodb:27017/chatappinsideapp. - Healthchecks for both services. No external DB port is exposed.
Notes:
- You do NOT need to set
VITE_SOCKET_URLif the frontend and backend run in the same container behind the Coolify domain; the app will use same-origin in production. - Message retention: MongoDB TTL auto-purges chat messages after 90 days; media files under
uploads/remain on disk. - Updates: simply push to Git; Coolify rebuilds and redeploys the
appservice while keeping themongodb_dataanduploadsvolumes intact. - Optional backups: schedule
mongodumpor use Coolify’s backup features for themongodb_datavolume.
If you prefer a single container that bundles the Node app and MongoDB, use Dockerfile.allinone with supervisord.
docker build -f Dockerfile.allinone -t chat-aio .
docker run -d --name chat-aio \
-p 3000:3000 \
-v chat_db:/data/db \
-v chat_uploads:/app/uploads \
chat-aio
# Verify health
curl -s http://localhost:3000/healthExpected response:
{ "status": "ok", "mongo": "connected" }- New → Application → From Git → set Dockerfile path:
Dockerfile.allinone. - Volumes:
/data/db→ persistent volume (MongoDB data)/app/uploads→ persistent volume (media uploads)
- Env: none required for Mongo;
server.jsdefaultsMONGO_URItomongodb://127.0.0.1:27017/chatappinside the container. - Domain → issue TLS.
- Deploy, then open
https://your-domain/health.
Checklist after deploy:
- Health:
{ status: "ok", mongo: "connected" }. - Send a message, upload an image/audio.
- Restart the app → messages remain (Mongo volume) and media persists (
/app/uploadsvolume).
- Check logs:
- Mongo:
Waiting for connectionson127.0.0.1:27017. - App:
Connected to MongoDBand[Mongo] Source: local default 127.0.0.1.
- Mongo:
- If
ECONNREFUSED 127.0.0.1:27017:- Ensure
MONGO_URIis not set to a different host; leaving it unset uses localhost. - Verify
/data/dbis writable (if using volumes, ensure mounts are correct or temporarily disable them to test). - On ARM hosts, the all-in-one image auto-selects the correct MongoDB binary (aarch64) — no extra steps needed.
- The image installs required MongoDB runtime libs (libcurl4, liblzma5, libsnappy1v5, libzstd1, libssl3, libgcc-s1). If you built before 2025-08-21, rebuild to include these.
- In the container shell, verify the binary and dependencies:
ldd /opt/mongodb/bin/mongod(check for “not found”)/opt/mongodb/bin/mongod --versionnc -z 127.0.0.1 27017 || true(returns 0 when mongod is listening)
- Redeploy and re-check
/health.
- Ensure
If /health shows { "status": "ok", "mongo": "connecting" } for more than ~30s, mongod likely failed to start. Collect logs (mongod + app) and confirm the above dependency checks.
Notes:
- Use the Compose method if you want MongoDB as a separate service; use all-in-one for simplicity.
- Retention: messages expire after 90 days via TTL index; media on disk persists until you delete it.
- The all-in-one image auto-detects CPU (amd64/arm64) and downloads the matching MongoDB binary; no platform-specific config.
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost"Then set:
SSL_KEY_PATH=./key.pemSSL_CERT_PATH=./cert.pem
- Browsers will block insecure WebSocket (ws://) connections from HTTPS pages. Always use HTTPS/WSS in production.
- Ensure your certificates are valid and readable by the server process.
src/components/— React components for chatroom, messages, avatars, user modal, etc.server.js— Node.js backend for real-time communication and state managementplan.md— Architecture plan and implementation notes
See plan.md for a detailed architecture plan, implementation mandate, and troubleshooting notes.