Portfolio
Loading Shiham's portfolio
Preparing the latest projects, skills, and contact paths with a quiet production-ready polish.
Initializing portfolio
Projects · Skills · Contact
Portfolio
Preparing the latest projects, skills, and contact paths with a quiet production-ready polish.
Initializing portfolio
Projects · Skills · Contact
Toki is a personal full-stack learning project built for people who want direct one-to-one conversations in a browser. It focuses on the core workflow of account creation, session restoration, discovering other registered users, and continuing previous chats.
Users can exchange text and image messages, update profile pictures, view recent conversations with latest-message previews, and see which contacts are online. Messages are persisted in MongoDB, delivered in real time through Socket.IO, and shown immediately with optimistic client updates.
Related projects
Jan 2026 – Jun 2026
A multi-vendor commerce platform connecting buyers, sellers, and administrators through dedicated storefronts, dashboards, payments, chat, and personalized discovery.
A React single-page application communicates with an Express REST API through credentialed Axios requests and with Socket.IO over the same backend server. MongoDB stores users and messages, while Cloudinary and Resend provide image hosting and signup email delivery. JWT cookie middleware protects both HTTP and socket connections.
A full-stack MERN chat application with real-time messaging, JWT authentication, image sharing, and production-grade security.
Live Demo: https://toki-frontend-tawny.vercel.app/
Runtime: Node.js 22+
Frontend: React 19
Database: MongoDB Atlas
Real-Time Communication: Socket.IO 4.x
License: ISC
| Package | Purpose |
|---|---|
| Express | REST API framework |
| MongoDB + Mongoose | Database and ODM |
| Socket.IO | Real-time bidirectional messaging |
| JWT + bcryptjs | Authentication and password hashing |
| Cloudinary | Image upload and CDN hosting |
| Resend | Transactional welcome email |
| Arcjet | Rate limiting, bot detection, shield |
| Pino | Structured JSON logging |
| cookie-parser | HTTP-only cookie handling |
| Package | Purpose |
|---|---|
| React 19 + Vite | UI framework and build tool |
| Zustand | Global state management |
| Socket.IO Client | Real-time connection |
| Axios | HTTP client with credentials |
| React Router v7 | Client-side routing |
| Tailwind CSS v4 | Utility-first styling |
| Framer Motion | Animations and transitions |
| React Hook Form | Form validation |
| Lucide React | Icon library |
| React Hot Toast | Toast notifications |
┌─────────────────────────────────────────────────────────┐
│ Browser (React SPA) │
│ │
│ Zustand Store ←→ Axios (REST) ←→ Socket.IO Client │
│ useAuthStore /api/* ws://backend │
│ useChatStore │
└────────────────────┬────────────────────────────────────┘
│ HTTPS + WSS
┌────────────────────▼────────────────────────────────────┐
│ Express + Socket.IO Server │
│ │
│ Arcjet Middleware → Route Handlers → Controllers │
│ │
│ /api/auth/* /api/messages/* /api/health │
│ signup, login, send, contacts, │
│ logout, profile chats, history │
│ │
│ Socket.IO (JWT cookie auth middleware) │
│ userId → Set<socketId> (multi-tab aware) │
└──────────┬─────────────────────┬───────────────────────┘
│ │
┌──────▼──────┐ ┌─────────▼──────────┐
│ MongoDB │ │ External Services │
│ Atlas │ │ Cloudinary (imgs) │
│ users │ │ Resend (email) │
│ messages │ │ Arcjet (security) │
└─────────────┘ └────────────────────┘
token as an HTTP-only cookiewithCredentials: trueuserId → Set<socketId> (supports multi-tab)sendMessage, backend saves to MongoDB, then emits newMessage to all receiver socketstoki/
├── backend/
│ ├── src/
│ │ ├── controllers/
│ │ │ ├── auth.controller.js # signup, login, logout, updateProfile
│ │ │ └── message.controller.js # sendMessage, getMessages, getChatPartners
│ │ ├── lib/
│ │ │ ├── arcjet.js # rate limit + bot detection config
│ │ │ ├── cloudinary.js # Cloudinary client init
│ │ │ ├── config.js # env var parsing and exports
│ │ │ ├── db.js # MongoDB connection
│ │ │ ├── logger.js # Pino logger
│ │ │ ├── resend.js # Resend client init
│ │ │ └── socket.js # Socket.IO server + online user map
│ │ ├── middleware/
│ │ │ ├── arcjet.middleware.js # Arcjet route protection
│ │ │ ├── auth.middleware.js # JWT cookie verification
│ │ │ └── socket.auth.middleware.js # Socket.IO JWT auth
│ │ ├── models/
│ │ │ ├── user.model.js
│ │ │ └── message.model.js
│ │ ├── routes/
│ │ │ ├── auth.route.js
│ │ │ └── message.route.js
│ │ ├── utils/
│ │ │ ├── authCookie.js
│ │ │ ├── generateTokenAndSetCookie.js
│ │ │ ├── validators.js
│ │ │ └── email/
│ │ │ ├── emailHandler.js
│ │ │ └── createWelcomeEmailTemplate.js
│ │ └── server.js
│ ├── .env.example
│ └── package.json
│
├── frontend/
│ ├── public/
│ │ ├── avatar.png
│ │ └── sounds/ # keystroke, notification, click audio
│ ├── src/
│ │ ├── components/ # chatContainer, chatHeader, messageInput…
│ │ ├── hooks/
│ │ │ └── useKeyboardSound.js # random keystroke sound player
│ │ ├── layouts/
│ │ │ └── rootLayout.jsx
│ │ ├── lib/
│ │ │ ├── axios.js # Axios instance with withCredentials
│ │ │ └── config.js
│ │ ├── pages/
│ │ │ ├── chatPage.jsx
│ │ │ ├── loginPage.jsx
│ │ │ └── signUpPage.jsx
│ │ ├── store/
│ │ │ ├── useAuthStore.js # auth state + socket lifecycle
│ │ │ └── useChatStore.js # messages, chats, contacts, sounds
│ │ ├── utils/
│ │ │ └── socket.js # Socket.IO client factory
│ │ └── App.jsx
│ ├── .env.example
│ ├── vercel.json # SPA rewrite rule for Vercel
│ └── package.json
│
└── README.md
git clone https://github.com/theShihamAhamed/MERN_Chat_app.git
cd MERN_Chat_app
npm install --prefix backend
npm install --prefix frontend
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env
Edit backend/.env with your values (see Environment Variables below).
The frontend .env defaults to http://localhost:5000 — no changes needed for local dev.
Backend (runs on port 5000):
npm run dev --prefix backend
Frontend (runs on port 5173):
npm run dev --prefix frontend
http://localhost:5173
backend/.env# Server
NODE_ENV=development
PORT=5000
LOG_LEVEL=info
TRUST_PROXY=false
# Database
MONGODB_URI=mongodb://127.0.0.1:27017/Toki
# Auth
JWT_SECRET=replace-with-a-long-random-secret
# CORS / Cookie
CLIENT_URL=http://localhost:5173
COOKIE_SECURE=false
COOKIE_SAME_SITE=lax
SERVE_FRONTEND=false
# Resend (welcome email)
RESEND_API_KEY=re_xxxxx
EMAIL_FROM=onboarding@yourdomain.com
EMAIL_FROM_NAME=Toki
# Cloudinary (image uploads)
CLOUDINARY_CLOUD_NAME=your-cloud-name
CLOUDINARY_API_KEY=your-api-key
CLOUDINARY_API_SECRET=your-api-secret
# Arcjet (security)
ARCJET_KEY=ajkey_xxxxx
ARCJET_MODE=DRY_RUN
# Upload limits
MAX_IMAGE_UPLOAD_BYTES=5242880
JSON_BODY_LIMIT=10mb
Key variable notes:
| Variable | Description |
|---|---|
MONGODB_URI | Local MongoDB or MongoDB Atlas connection string |
JWT_SECRET | Long random string — never commit the real value |
CLIENT_URL | Frontend origin allowed by CORS. Comma-separate multiple URLs |
COOKIE_SECURE | false for HTTP locally; true for HTTPS production |
COOKIE_SAME_SITE | lax for same-domain; none for cross-domain frontend/backend |
TRUST_PROXY | Set to 1 on hosted platforms (Render, Railway, etc.) |
frontend/.env# Only needed for split frontend/backend deployments
VITE_API_BASE_URL=http://localhost:5000/api
VITE_BACKEND_URL=http://localhost:5000
For same-domain production (Express serving the SPA), both variables can be omitted.
/api/auth| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST | /signup | — | Register a new user |
POST | /login | — | Authenticate and set cookie |
POST | /logout | — | Clear auth cookie |
GET | /profile | ✅ | Get current user profile |
PUT |
/api/messages| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST | /send/:id | ✅ | Send a message to user :id |
GET | /chats | ✅ | Get chat partners with latest messages |
GET | /contacts | ✅ | Get all users (for starting new chats) |
GET | /:id | ✅ | Get full message history with user :id |
| Method | Endpoint | Description |
|---|---|---|
GET | /api/health | Returns 200 OK — use for deployment health checks |
This is the recommended approach for a portfolio deployment. The React frontend lives on Vercel; the Express API runs on Render with WebSocket support.
Render backend:
| Setting | Value |
|---|---|
| Root Directory | backend |
| Build Command | npm install |
| Start Command | npm start |
| WebSocket support | Required — enable in Render settings |
Backend environment variables on Render:
NODE_ENV=production
PORT=10000
TRUST_PROXY=1
SERVE_FRONTEND=false
CLIENT_URL=https://your-frontend.vercel.app
COOKIE_SECURE=true
COOKIE_SAME_SITE=none
# ... plus MongoDB, JWT, Cloudinary, Resend, Arcjet
Vercel frontend:
VITE_API_BASE_URL=https://your-backend.onrender.com/api
VITE_BACKEND_URL=https://your-backend.onrender.com
vercel.json in frontend/ already includes the SPA rewrite rule — no extra config needed.
Cross-domain cookie note: Both frontend and backend must be on HTTPS. Axios and Socket.IO must send credentials.
COOKIE_SAME_SITE=noneis required for cross-domain cookies.
Express serves both the API and the compiled React SPA from the same process and domain.
Build command:
npm run build
Start command:
npm start
Backend environment variables:
NODE_ENV=production
PORT=<platform-assigned>
TRUST_PROXY=1
SERVE_FRONTEND=true
CLIENT_URL=https://your-app.example.com
COOKIE_SECURE=true
COOKIE_SAME_SITE=lax
Leave VITE_API_BASE_URL and VITE_BACKEND_URL empty — the frontend defaults to relative paths.
| Environment | COOKIE_SECURE | COOKIE_SAME_SITE | CLIENT_URL |
|---|---|---|---|
| Local dev | false | lax | http://localhost:5173 |
| Same-domain production | true | lax | https://your-app.com |
| Cross-domain production | true | none | https://your-frontend.vercel.app |
Logout clears the token cookie using the same options as login, so the browser removes it correctly.
Arcjet protects all /api/auth/* and /api/messages/* routes with:
Without ARCJET_KEY, the middleware no-ops safely — useful for local dev without an account. Arcjet errors also fail open so a provider outage never takes your API offline.
# Start in dry-run to observe decisions without blocking
ARCJET_MODE=DRY_RUN
# Switch to live once you've reviewed the Arcjet dashboard
ARCJET_MODE=LIVE
# Install all dependencies
npm install --prefix backend && npm install --prefix frontend
# Development
npm run dev --prefix backend # Start backend with nodemon
npm run dev --prefix frontend # Start frontend with Vite HMR
# Lint
npm run lint --prefix backend # Syntax check (backend)
npm run lint --prefix frontend # ESLint (frontend)
# Production build
npm run build --prefix frontend # Build frontend to frontend/dist
npm run build # Install deps + build frontend (root)
npm start # Start production backend
# Health check
curl http://localhost:5000/api/health
Cookie not set after login
COOKIE_SECURE=false for local HTTP (true requires HTTPS)COOKIE_SAME_SITE=none and ensure both sides are on HTTPSwithCredentials: trueCORS error
CLIENT_URL must be the exact frontend origin (e.g. https://your-app.vercel.app) — no trailing slashCLIENT_URL=https://a.com,https://b.comSocket.IO won't connect
VITE_BACKEND_URL must point to the backend originImages fail to upload
JSON_BODY_LIMIT if large base64 payloads are rejected (default 10mb)Welcome email not delivered
RESEND_API_KEY and that EMAIL_FROM uses a verified sender domain in ResendArcjet blocking unexpectedly
ARCJET_MODE=DRY_RUN to log decisions without blocking trafficTRUST_PROXY is correct for your host (usually 1 on Render, Railway, etc.)Current limitations:
Planned improvements:
ISC License
Built by Shiham Ahamed
The case-study preview is collapsed. Activate the button to make the full content available.
Aug 2025 – Oct 2025
A marketplace for verified Sri Lankan homestays, supporting guest bookings, host onboarding, admin approval, and test-mode payments.
Oct 2025 – Nov 2025
A full-stack developer Q&A platform where programmers can ask, answer, vote, save questions, explore tags, and generate AI-assisted responses.
SERVE_FRONTEND | true only when frontend/dist is present in the backend service |
ARCJET_MODE | DRY_RUN to observe, LIVE to enforce |
/update-profile |
| ✅ |
| Upload new profile picture |