A real-time team chat application built with React, Node.js, and Socket.IO. Think Slack-lite: one team, multiple channels, real-time messaging, invite codes, and a polished UI.
Built end-to-end using a spec-first TDD workflow with Claude Code.
- Auth — Register, login, and persistent sessions via JWT with httpOnly cookies
- Teams & Channels — Create a team, get a
#generalchannel automatically, create more channels - Invite Codes — Generate shareable codes so others can join your team; set expiry and max-use limits
- Real-Time Messaging — Send and receive messages instantly via Socket.IO, with message history and pagination
- Typing Indicators — See when others are typing; clears automatically after 3 seconds
- Online Presence — Green dot on your avatar when connected
- Unread Badges — Per-channel unread counts, persisted on the server so they survive page refresh
- Avatar Popover — Click any message avatar to see the sender's profile
- Responsive Layout — Works on desktop, tablet, and mobile; sidebar slides in as an overlay on narrow screens
- Polish — Empty states, loading skeletons, reconnection banner when the connection drops
| Layer | Technology |
|---|---|
| Frontend | React 18, TypeScript, Vite, Zustand, Tailwind CSS |
| Backend | Node.js, Express, TypeScript |
| Real-time | Socket.IO |
| Database | PostgreSQL + Prisma ORM |
| Auth | JWT (access + refresh tokens, httpOnly cookies) |
| Testing | Vitest, React Testing Library, Supertest |
- Node.js 20+
- PostgreSQL running locally
# Clone the repo
git clone https://github.com/prakash-work/team-chat-app.git
cd team-chat-app
# Install server dependencies
cd server && npm install
# Set up environment variables
cp .env.example .env
# Edit .env — set DATABASE_URL, ACCESS_TOKEN_SECRET, REFRESH_TOKEN_SECRET
# Run database migrations
npx prisma migrate dev
# Install client dependencies
cd ../client && npm install# Terminal 1 — server (port 3000)
cd server && npm run dev
# Terminal 2 — client (port 5173)
cd client && npm run devOpen http://localhost:5173.
# Server tests (172 tests)
cd server && npx vitest run
# Client tests (301 tests)
cd client && npx vitest run├── client/ # React frontend
│ ├── src/
│ │ ├── components/ # AppShell, Sidebar, MessageList, MessageInput, ...
│ │ ├── pages/ # LoginPage, RegisterPage, NoTeamPage
│ │ ├── store/ # Zustand stores (auth, channel, message, socket, ...)
│ │ ├── api/ # Fetch wrappers (auth, channel, team)
│ │ └── utils/ # avatar.ts, formatTimestamp.ts
├── server/ # Express + Socket.IO backend
│ ├── src/
│ │ ├── auth/ # Register, login, token refresh
│ │ ├── team/ # Team creation, membership, invite codes
│ │ ├── channel/ # Channels, message history, unread state
│ │ ├── chat/ # Socket.IO room management, messaging, typing, presence
│ │ └── lib/ # Error classes, HTTP error handler, socket instance
│ └── prisma/
│ ├── schema.prisma
│ └── migrations/
└── docs/
└── specs/ # Discovery doc, phase specs, TDD plans
The server follows a simple module-per-feature structure: auth/, team/, channel/, chat/. Each module owns its routes, service functions, and tests. The client mirrors this with Zustand stores per concern and React Testing Library tests co-located in __tests__/.
Real-time events flow through a single Socket.IO namespace. The server maintains an in-memory online presence set (not persisted) and stateless typing indicators. Unread counts are the only real-time state persisted to the database.
MIT