TalkNest is a full-stack real-time chat MVP built from the PRD. It includes an Expo React Native app, a Node.js Socket.io backend, SQLite persistence, JWT auth with signup, personal handles, attachment messages, timestamps generated on the server, a dark theme, and a Vite/Three.js landing page.
apps/server- Express, Socket.io, JWT auth, registration, SQLite vianode:sqlite, Zod validation, attachment validation, and integration tests.apps/mobile- Expo React Native signup/login and chat app with a sidebar inbox, persisted sessions, message history, direct chats by handle, attachment sending, dark theme, logout, and connection state.apps/web- Vite React landing page with a responsive Three.js hero scene, product preview, app CTA, and dark theme toggle.
- Node.js
>=22.5.0 - npm
node:sqlite is currently experimental in Node, so the server may print an experimental warning during tests or startup.
npm install
cp apps/server/.env.example apps/server/.envNo demo users are seeded. Create an account in the app, then create a second account in another browser/session if you want to test personal chat locally.
- Users get a unique handle and can open one-to-one chats by entering another user handle.
- The main app uses a left rail, chat list, and active conversation layout inspired by modern messaging apps.
- The app rail and chat list sidebar can be collapsed independently.
- Home, Chat, Contacts, Notifications, Calendar, and Settings nav items open dedicated panels.
- Settings supports dark theme plus editable display name, username, handle, and email.
- Messages can include text, a photo, a video, or one document attachment.
- Supported attachment types: JPEG, PNG, WebP, GIF, MP4, MOV, WebM, PDF, DOC, DOCX, PPT, PPTX, and TXT.
- Size limits: photos and videos up to 4 MB; documents up to 2 MB.
- Dark theme can be changed from Settings or toggled with
Command+Don macOS andCtrl+Delsewhere.
Start all runtimes:
npm run devUseful individual commands:
npm run dev:server
npm run dev:web
npm run dev:mobileDefault URLs:
- Backend API:
http://localhost:4000 - Landing page:
http://localhost:5173 - Expo web/mobile dev server: shown by Expo in the terminal
The landing page links to the main app with VITE_TALKNEST_APP_URL. It defaults to http://127.0.0.1:8081 for local Expo web:
VITE_TALKNEST_APP_URL=http://127.0.0.1:8081 npm run dev:webFor a physical device, set the mobile app API URL to a LAN-reachable backend:
EXPO_PUBLIC_API_URL=http://YOUR_LAN_IP:4000 npm run dev:mobilenpm run typecheck
npm run test
npm run build
npm run verifyVisual validation for the Three.js landing page requires the web dev server:
npm run dev:web
npm run test:visual:webThe visual check saves screenshots in apps/web/artifacts/ and verifies that the Three.js canvas is not blank on desktop and mobile viewports.
This repository is configured to deploy the Vite landing page to Vercel from the repo root.
npm run deploy:vercelThe Vercel build uses:
- Build command:
npm run build -w @talknest/web - Output directory:
apps/web/dist - Config file:
vercel.json
The real-time chat backend should be deployed separately to a long-running Node host because Socket.io WebSockets and SQLite persistence are not a good fit for Vercel serverless functions. After the backend is hosted, point the mobile app to it with EXPO_PUBLIC_API_URL.
GET /health- service healthPOST /api/auth/register- creates a user and returns{ token, user }POST /api/auth/login- returns{ token, user }GET /api/auth/me- validates a bearer tokenPATCH /api/auth/me- updates display name, username, handle, or email and returns a refreshed{ token, user }GET /api/users- returns public users and their handlesPOST /api/direct-conversations/resolve- accepts{ handle }and returns a private room for the authenticated user and that handleGET /api/messages?roomId=lobby- returns recent room messages
Socket.io events:
- Client emits
room:joinwith{ roomId }before opening a private room - Client emits
message:sendwith{ roomId, text, attachment? } - Server broadcasts
message:newwith message ID, sender, text, optional attachment, type, room, and timestamp - Server emits
socket:readyafter authenticated connection
The shared lobby uses roomId=lobby. Personal chat room IDs are generated by the backend after resolving a user handle. Only the two participants can load history, join the room, or send messages in that personal conversation.