A real-time, passwordless chat platform β add friends by email and start talking instantly.
- Overview
- Features
- Tech Stack
- Architecture
- Getting Started
- Environment Variables
- Available Scripts
- Real-time Events
- Project Structure
- Security Notes
- Roadmap
- Author
Poking App is a full-stack chat application. There are no passwords β you sign in with your email and a one-time code (OTP). Once in, you add friends by their email address and chat in real time, with typing indicators, online presence, and read receipts.
π Live: client on pokingapp.vercel.app Β· API on Render
- π Passwordless auth β email β 6-digit OTP β JWT session
- π¬ Real-time messaging over WebSockets (Socket.IO)
- βοΈ Typing indicators β see when a friend is typing
- π’ Online presence & last seen β live status dots, "last seen β¦"
- ββ Read receipts β single tick (sent) β double tick (read)
- β‘ Optimistic send β messages appear instantly with a sending/sent/read state
- π₯ Friend system β send / accept / decline requests, delete friends
- π Search your chats, with most-recent conversations first
- πͺͺ Profile page β display name, username, avatar
- π¨ Polished UX β skeleton loaders, smooth animations, dark theme, responsive layout
- User avatars / photo upload
- File & image sharing in chat
- Group chats
| Layer | Technologies |
|---|---|
| Frontend | React 18, Vite, Tailwind CSS, Socket.IO Client, Framer Motion, React-Toastify, React-Icons, Moment, js-cookie |
| Backend | Node.js, Express, Socket.IO, Mongoose (MongoDB), JSON Web Tokens |
| Email (OTP) | Brevo HTTP API (no SMTP β works on hosts that block SMTP) |
| Security/Perf | Helmet, CORS, express-rate-limit, express-mongo-sanitize, compression (gzip) |
| Hosting | Vercel (client), Render (API), MongoDB Atlas (database) |
ββββββββββββββ HTTPS / WSS ββββββββββββββββββββββββββββ ββββββββββββββββ
β Client β βββββββββββββββββΆ β Express API β ββββΆ β MongoDB Atlasβ
β (React/Viteβ REST + Socket β + Socket.IO (one server)β ββββββββββββββββ
β Vercel) β β Render β
ββββββββββββββ ββββββββββββββ¬ββββββββββββββ
β HTTPS
βΌ
βββββββββββββββββ
β Brevo (OTP) β
βββββββββββββββββ
- A single HTTP server serves both the REST API and the Socket.IO connection.
- Auth is JWT: issued after OTP verification, sent as a
Bearertoken for REST and in the socket handshake (auth.token) for the websocket β the server rejects unauthenticated sockets and derives the user id from the token (clients can't spoof a sender). - Online presence is tracked in memory (single instance). See Security Notes.
- Node.js β₯ 18
- A MongoDB connection string (e.g. MongoDB Atlas)
- A free Brevo account + API key and a verified sender email
git clone https://github.com/MeMoElprince/poking-app.git
cd poking-appcd server
npm install
cp env.example .env # then fill in the values (see below)
npm run dev # development (nodemon)
# npm start # production (node server.js)cd client
npm install
# create client/.env with VITE_API_URL (see below)
npm run dev # Vite dev server
npm run build # production build β dist/βΉοΈ The server reads its config from
server/.env(copied fromenv.example).
| Variable | Required | Description |
|---|---|---|
DATABASE |
β | MongoDB connection string (use <PASSWORD> placeholder for the password) |
DATABASE_PASSWORD |
β | DB password, substituted into DATABASE at startup |
JWT_SECRET |
β | Secret used to sign JWTs |
JWT_EXPIRES_IN |
β | Token lifetime, e.g. 90d |
BREVO_API_KEY |
β | Brevo API key for sending OTP emails |
BREVO_SENDER_EMAIL |
β | A sender address verified in Brevo |
BREVO_SENDER_NAME |
β | Display name for the sender (defaults to PokingApp) |
CLIENT_URL |
β | Restrict CORS to this origin (defaults to *) |
PORT |
β | Server port (defaults to 2000, or Render's assigned port) |
| Variable | Required | Description |
|---|---|---|
VITE_API_URL |
β | Base URL of the API, with a trailing slash β e.g. https://your-api.onrender.com/ |
| Script | Action |
|---|---|
npm start |
Run in production (node server.js) |
npm run dev |
Run with nodemon, NODE_ENV=development |
npm run prod |
Run with nodemon, NODE_ENV=production |
| Script | Action |
|---|---|
npm run dev |
Start the Vite dev server |
npm run build |
Build for production into dist/ |
npm run preview |
Preview the production build |
npm run lint |
Run ESLint |
All socket connections require a valid JWT in the handshake: io(url, { auth: { token } }).
Client β Server
| Event | Payload | Purpose |
|---|---|---|
join-room |
roomId |
Subscribe to a chat room and load its latest messages |
load-older |
{ room, before } |
Fetch older messages before a timestamp (pagination) |
send-message |
{ room, message }, ack |
Persist & broadcast a message; ack returns the saved doc |
mark-read |
{ room } |
Mark the room's incoming messages as read |
typing / stop-typing |
{ room } |
Relay typing state to the other participant |
get-friends |
userId |
Ask the server to push a fresh friends list to a user |
friend-request-sent / number-friend-request |
userId |
Notify a user of a new/changed friend request |
Server β Client
| Event | Payload | Purpose |
|---|---|---|
presence:init |
[userId] |
Snapshot of who is currently online |
presence |
{ userId, online, lastSeen? } |
Live online/offline updates |
get-messages |
[message] |
A room's latest messages |
older-messages |
{ room, messages } |
Paged older messages |
receive-message |
message |
A new message in the open room |
message-notification |
{ room, message, senderId } |
New-message hint for the chat list (preview / unread) |
messages-read |
{ room } |
Recipient read your messages β flip ticks to ββ |
typing / stop-typing |
{ room, userId } |
The other participant's typing state |
poking-app/
βββ client/ # React + Vite frontend
β βββ src/
β βββ Components/ # Pages, RootComponents (Left/Right/Sidebar), UiComponents
β βββ Store/ # Context (User, Friends, Presence, BackDrop), socket.js, urls.js
β βββ ...
βββ server/ # Express + Socket.IO backend
βββ app.js # Express app, middleware & all Socket.IO handlers
βββ server.js # DB connection + HTTP server bootstrap
βββ controllers/ # auth, user, message, global error handler
βββ models/ # User, Room, Message (Mongoose schemas)
βββ routes/ # REST routes (/api/v1/users)
βββ utils/ # email (Brevo), token factory, error helpers
βββ views/email/ # Pug templates for emails
- JWT-authenticated sockets β unauthenticated connections are rejected; the message sender is taken from the token, not the client payload.
- Helmet sets hardened HTTP headers; CORS can be locked to
CLIENT_URL. - Rate limiting (200 req/min per IP) on
/api, withtrust proxyenabled so client IPs are read correctly behind Render's proxy. - express-mongo-sanitize strips
$/.operators to prevent NoSQL injection; JSON bodies are capped at10kb. - Presence is in-memory, which is correct for a single instance. To scale horizontally, add the Socket.IO Redis adapter and move presence to a shared store.
- Avatar / photo upload
- File & image messages
- Group chats
- Message editing & deletion
- Push notifications
Mustafa Elsharawy β @MeMoElprince
Contributions and issues are welcome. If you find this project useful, consider giving it a β.