Lifeline
A Bangladesh-focused PWA for finding donors by district, posting emergency blood requests, running local communities, and chatting in real time — with a volunteer/admin moderation layer.
- >Custom JWT access tokens plus HttpOnly refresh sessions (not a hosted auth SaaS)
- >Hyper-local donor search across Bangladesh division, district, and upazila
- >Feed ranking, Socket.IO chat, and Next.js rewrites that proxy API + websockets

Project Vision & Meaning
In Bangladesh, urgent blood requests still move through Facebook groups and forwarded phone numbers. That is slow, unscoped, and unsafe: a Dhaka post reaches people who cannot donate in time, and a stranger's number is the only coordination channel. Lifeline is a hyper-local community network — donors, requests, communities, and chat — so a request is visible to people who actually live in that district, not to an unbounded social graph.
Real-World Problems Solved
Guests can search donors by geography and blood group without an account. Members post emergency requests into a ranked feed, join local communities, log donations, and coordinate in 1:1 or community chat. Volunteers and admins moderate reports, flagged posts, and events. The frontend is a PWA on Vercel; the API runs blue-green Docker on a VPS behind Nginx, with Next rewrites so browser cookies and Socket.IO share one origin in development and production.
Challenges & Solutions
Keeping Auth and WebSockets Same-Origin Across Vercel and a VPS
Problem Context:The UI deploys to Vercel; the Express API and Socket.IO server deploy to a VPS. If the browser called the API on a different host, HttpOnly refresh cookies needed a shared parent domain, CORS preflights broke mobile networks, and Socket.IO fell back to long-polling or failed entirely. A cookie-only session also could not travel as a Bearer token for background refetch.
Issued a short-lived JWT access token in memory and an opaque refresh token in an HttpOnly cookie. Next.js rewrites /api/v1/* and /socket.io/* to the internal backend URL so the browser talks to one origin. requireAuth accepts Bearer JWT first, then the refresh cookie. A client session gate serializes concurrent 401s through a single refresh. The Next middleware cookie is_logged_in is treated as UX routing only — authorization stays on the API.
Hyper-Local Matching Instead of a Global Donor List
Problem Context:A national donor dump is useless in an emergency. Bangladesh addressing is division → district → upazila/area, and a request in Chittagong should not rank a willing donor in Rangpur first. Naive ILIKE search across the user table ignored availability, blood group, and recency, and it could not power both the public Find Donors page and the authenticated feed.
Stored geo on the User model (division, district, upazila, area) plus donor availability fields. Donor search filters those dimensions together with blood group. The feed scores posts (emergency, geo, engagement) into a cachedScore so ranking is not recomputed on every scroll. Blood requests are a first-class PostType with units and location, not a generic text post with a hashtag.
Realtime Chat and Push Without Blocking the Request Path
Problem Context:Blood requests expire in hours. If chat is polling, coordinators miss replies. If every notification is sent inline on the request thread, a busy feed endpoint stalls. The product needed 1:1 and community rooms, unread notifications, and optional push — without making Redis a boot requirement for every environment.
Mounted Socket.IO on the same HTTP server as Express. Chat has REST history plus socket events for live delivery. Push uses VAPID web-push subscriptions. Background work (ranking, notifications) can run through BullMQ when Redis is present, and falls back to a sync queue driver so local and small VPS deploys do not hard-depend on Redis. Uploads go to Cloudflare R2 so avatars and post media never sit on the API container.
