A full-stack real-time messaging app built to showcase backend engineering skills: modular NestJS architecture, WebSocket communication, push notifications, CI/CD pipelines, and production deployment on a VPS.
- Real-time messaging — instant delivery via WebSocket, with sent → delivered → read status tracking
- Group chats — create groups, manage members, assign admin roles, upload group avatars
- Typing indicators & online presence — live updates across all connected clients
- Push notifications — Web Push API with VAPID via Service Worker, works in background
- Offline sync — messages cached in IndexedDB (Dexie), auto-synced on reconnect
- User management — registration, login, editable profiles with avatars, user blocking
- Theme customization — light/dark mode, 6 accent colors, customizable chat backgrounds and font sizes
- Responsive design — mobile, tablet, and desktop layouts
| Technology | Role |
|---|---|
| NestJS | Modular, decorator-based Node.js framework with dependency injection |
| PostgreSQL | Primary relational database |
| Prisma ORM | Type-safe DB client, schema-first migrations |
| Socket.io | WebSocket server — messages, presence, typing, read receipts |
| JWT + bcryptjs | Stateless authentication with hashed passwords |
| Web Push (VAPID) | Push notification delivery via Service Worker |
| Multer | Multipart file upload handling |
| @nestjs/schedule | Cron jobs for periodic background tasks |
| Vitest | Unit testing with mocked Prisma client |
| Technology | Role |
|---|---|
| React 18 | Component-driven UI with hooks |
| TypeScript | End-to-end type safety |
| Vite | Fast dev server and optimized builds |
| Tailwind CSS | Utility-first styling with CSS variable theming |
| Zustand | Lightweight reactive state management |
| Socket.io-client | WebSocket client |
| Dexie (IndexedDB) | Client-side message cache for offline support |
| Zod | Runtime schema validation |
| Vitest + Testing Library | Component and interaction testing |
| Technology | Role |
|---|---|
| pnpm workspaces | Monorepo managing server/, web/, shared/ packages |
| Shared package | DTOs and WebSocket event types consumed by both ends |
| GitHub Actions | CI/CD — test, build, deploy on every push |
| Nginx | Reverse proxy routing frontend/API, WebSocket upgrades |
| Let's Encrypt | Automated TLS certificate provisioning |
telegram-clone/ # pnpm workspace root
├── server/ # NestJS backend
│ ├── prisma/ # Schema + migrations
│ └── src/
│ ├── auth/ # JWT auth (global guard)
│ ├── chat/ # REST controller + Socket.io gateway
│ ├── user/ # User management
│ ├── notification/# Web Push service
│ ├── upload/ # Multer file handling
│ └── db/ # Prisma module
├── web/ # React frontend
│ └── src/
│ ├── components/ # UI components
│ ├── pages/ # Route-level pages
│ ├── stores/ # Zustand state stores
│ └── services/ # API, Socket, Dexie, notifications
├── shared/ # Shared TypeScript types & DTOs
└── e2e/ # End-to-end tests
- One gateway for all real-time events — a single
ChatGateway(@WebSocketGateway) handles all Socket.io events: message delivery, read receipts, typing indicators, and online/offline presence. - Shared type contracts — request/response DTOs and WebSocket event enums are defined once in
shared/and imported by both server and client, eliminating type drift. - Global auth guard — the JWT guard is registered globally in
AuthModule, with opt-out via a@Public()decorator for open endpoints. - Offline-first messaging — the frontend stores all messages in IndexedDB via Dexie. A sync service replays missed events when the WebSocket reconnects.
The ChatGateway handles the following Socket.io events:
| Event | Direction | Description |
|---|---|---|
send-message |
client → server | Persist and broadcast a new message |
message-delivered |
server → client | Notify sender when recipient comes online |
message-read |
bidirectional | Mark messages as read, notify sender |
typing |
client → server → peers | Relay typing state to chat members |
user-online |
server → clients | Broadcast when a user connects |
user-offline |
server → clients | Broadcast when a user disconnects |
Every push triggers a GitHub Actions workflow: install → test → build → deploy → DB migrations.
Two environments run on the same VPS:
| Environment | Branch | URL | Access |
|---|---|---|---|
| Production | master |
main domain | Public, SSL via Let's Encrypt |
| Development | develop |
subdomain | Password-protected at Nginx level |
Infrastructure:
- NestJS API, React static files, and PostgreSQL all hosted on a single VPS
- Nginx reverse proxy routes
/apito NestJS and everything else to the React build, with WebSocket upgrade support - Prisma migrations run automatically as part of the deployment script
# Run backend tests
cd server && pnpm test
# Run frontend tests
cd web && pnpm test
# Run with coverage
cd server && pnpm test:cov
cd web && pnpm test:cov- Backend — Vitest with mocked Prisma client; service-level unit tests for auth, chat, and user modules
- Frontend — Vitest +
@testing-library/react+@testing-library/user-event; component tests for chat and message input - E2E — Playwright-based tests in the
e2e/package
- Node.js ≥ 22
- pnpm
- PostgreSQL
git clone https://github.com/daniserrano7/telegram-clone.git
cd telegram-clone
pnpm install# Server
cp server/.env.example server/.env.dev
# Fill in: DATABASE_URL, JWT_SECRET, ALLOWED_ORIGINS, VAPID_* keys
# Web
cp web/.env.example web/.env
# Fill in: VITE_API_URL, VITE_WS_URL, VITE_VAPID_PUBLIC_KEYGenerate VAPID keys for push notifications:
npx web-push generate-vapid-keyscd server
pnpm prisma-push:dev # Push schema to DB (dev)# From root — starts both server and web in parallel
pnpm devApp runs at http://localhost:3000, API at http://localhost:5000/api.
- GitHub: @daniserrano7
This project is for portfolio/educational purposes and is not affiliated with Telegram.





