MediQueue solves two real-world healthcare problems in a single platform:
- CareQueue — eliminates physical waiting rooms with a real-time digital queue. Patients join remotely, track their position live, and get notified when it's their turn.
- Health-Vault — gives patients full ownership of their medical records with consent-based sharing, AES-256-GCM encryption at rest, and an immutable audit trail for every access.
Three user roles — Patient, Doctor, Admin — each with their own dashboard, workflows, and permission boundary.
| Concern | Solution |
|---|---|
| Real-time queue updates | Socket.io v4 rooms (user:<id>) — server pushes diffs on every queue state change |
| Medical record security | AES-256-GCM encryption via a dedicated backend service |
| Authentication | Two-token JWT (15 min access + 7 day refresh) with a two-step OTP registration flow; tokens kept in memory only — never written to localStorage |
| MFA | TOTP via speakeasy + backup codes; separate JWT_MFA_SECRET for MFA session tokens |
| Authorization | Stateless protect() + authorize(...roles) middleware; Redis-cached user lookup (60s TTL) to avoid DB hit on every request |
| Rate limiting | Redis-backed createRateLimiter() factory — per-user limits effective across multiple processes |
| Audit compliance | Immutable AuditLog model with SHA-256 integrity hashes; middleware wraps every sensitive admin action |
| Emergency access | Doctors can request override access; all overrides are logged and surfaced to admins for review |
| File uploads | Multer → Cloudinary storage with authenticated backend retrieval flows |
| AI triage | Groq LLaMA 3.1 suggests priority from symptoms — human override always preserved, AI is advisory only |
| AI summarization | On-demand PDF text extraction + LLaMA summary — quota-limited, PII-stripped before Groq sees any text |
| AI image analysis | Groq LLaMA 4 Scout (multimodal) reads consultation note images — confidence scoring flags unclear handwriting |
| Production hardening | Helmet CSP default-src 'none', CORS origin allowlist, 5xx message masking, SIGTERM/SIGINT graceful shutdown, Winston stdout-only in production |
- Register with email/SMS OTP verification (two-step flow)
- Book appointments and join a live queue remotely
- Real-time queue position tracking with estimated wait time
- Upload, view, and delete medical records (encrypted)
- Grant / revoke per-doctor consent to individual records
- View prescriptions from completed consultations
- In-app notification centre with real-time push
- Live queue dashboard — call next patient, manage consultation flow
- Access patient records (consent-gated or emergency override with justification)
- Write and manage prescriptions
- View shared records and appointment history
- Receive real-time notifications for queue events
- Full user management (create, suspend, promote, delete)
- Analytics dashboard — appointments, queue throughput, system usage
- Audit log explorer with filtering (who accessed what, when, why)
- Emergency access review — approve / reject doctor override requests
- Real-time system activity monitoring
- ES Modules throughout the backend (
"type": "module") — nativeimport/export, all relative imports require.jsextensions. - API versioning — all routes mounted at
/api/v1/; health check available at/health,/api/health, and/api/v1/health. - Redis-backed caching & rate limiting —
iorediswithlazyConnect: true; user auth cache (60s TTL), analytics cache (5 min TTL), all rate limiters share the same Redis instance. - Stateless auth —
protect()middleware verifies the JWT then callsgetCachedUser()(Redis → DB fallback); no full DB hit on hot paths. ioshared viaapp.set('io', io)— controllers emit real-time events without coupling to the Socket layer.- Tokens in memory only — access tokens live in a module-level variable inside
api.js(Axios interceptor). The Zustandauth-storagepersists only non-sensitive user info (name, role, avatar) — never tokens. - File proxy —
GET /api/v1/records/:id/view-filefetches the file from Cloudinary server-side and streams it to the client withContent-Type: application/pdfandContent-Disposition: inline. The raw Cloudinary URL is never given to the browser.
| Prefix | Responsibility |
|---|---|
POST /api/v1/auth |
Register (OTP 2-step), login, MFA, refresh token, forgot/reset password |
GET/PUT /api/v1/users |
Profile management |
GET/POST/DELETE /api/v1/appointments |
Booking lifecycle; available slots |
GET/POST /api/v1/queue |
Join queue, call-next, queue stats |
POST /api/v1/queue/triage |
AI symptom triage — advisory suggestion only, never sets priority automatically |
GET/POST/DELETE /api/v1/records |
Encrypted file upload, list, download, share, PDF export |
POST /api/v1/records/:id/summarize |
On-demand AI summary of a text-based PDF or image record |
GET /api/v1/records/:id/view-file |
Stream file bytes through the backend with correct Content-Type |
GET/POST/DELETE /api/v1/consent |
Grant, revoke, and query consent grants |
GET/POST /api/v1/emergency-access |
Doctor override requests + admin review |
GET/POST /api/v1/prescriptions |
Create and view prescriptions |
GET /api/v1/audit |
Tamper-evident audit log queries (admin) |
GET /api/v1/analytics |
Dashboard metrics (Redis-cached 5 min) |
GET/POST/PUT/DELETE /api/v1/admin |
User and emergency case management |
GET/POST /api/v1/notifications |
Real-time notification inbox |
Backend: Node.js 18 · Express · MongoDB (Mongoose) · Redis (ioredis) · Socket.io v4 · JWT · bcrypt · Multer · Cloudinary · Winston · node-cron · Nodemailer · Twilio
Frontend: React 18 · Vite · Tailwind CSS · Zustand · React Query · Axios · Socket.io-client · React Router v6
Security: AES-256-GCM (medical records) · JWT dual-token + MFA (TOTP) · OTP via SMS + email · RBAC · Helmet (default-src 'none') · Redis rate limiting · Immutable audit log · CORS origin allowlist
- Option 1 (Docker): Docker & Docker Compose
- Option 2 (Manual): Node.js v18+, MongoDB, Redis, npm
# 1. Clone the repo
git clone https://github.com/piyushkumar0707/MediQueue.git
cd MediQueue
# 2. Configure environment
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env
# Edit both .env files with your values (see table below)
# 3. Start all services
docker-compose up --build
# App is now running at:
# - Frontend: http://localhost:80
# - Backend API: http://localhost:5000
# - MongoDB: localhost:27017
# - Redis: localhost:6379Use shell environment variables to override frontend build-time endpoints and backend CORS origin without editing the compose file.
# PowerShell example
$env:VITE_API_URL="http://localhost:5000/api/v1"
$env:VITE_SOCKET_URL="http://localhost:5000"
$env:FRONTEND_URL="http://localhost"
docker-compose up --build# Bash example
VITE_API_URL=http://localhost:5000/api/v1 \
VITE_SOCKET_URL=http://localhost:5000 \
FRONTEND_URL=http://localhost \
docker-compose up --build- If compose fails before startup, ensure backend/.env exists:
- cp backend/.env.example backend/.env
- Backend validates required variables at boot. Confirm these are set in backend/.env:
- MONGODB_URI, REDIS_URL, JWT_ACCESS_SECRET, JWT_REFRESH_SECRET, JWT_MFA_SECRET, ENCRYPTION_KEY
- ENCRYPTION_KEY must be exactly 32 characters in production mode.
# 1. Clone
git clone https://github.com/piyushkumar0707/MediQueue.git
cd MediQueue
# 2. Backend
cd backend
cp .env.example .env # fill in all required vars (see table below)
npm install
npm run dev # → http://localhost:5000
# 3. Frontend (new terminal)
cd frontend
cp .env.example .env # set VITE_API_URL and VITE_SOCKET_URL
npm install
npm run dev # → http://localhost:5173| Variable | Required | Description |
|---|---|---|
MONGODB_URI |
✅ | MongoDB connection string |
REDIS_URL |
✅ | Redis connection string (e.g. redis://localhost:6379) |
JWT_ACCESS_SECRET |
✅ | Sign access tokens (15 min) |
JWT_REFRESH_SECRET |
✅ | Sign refresh tokens (7 days) |
JWT_MFA_SECRET |
✅ | Sign MFA session tokens |
ENCRYPTION_KEY |
✅ | Exactly 32 characters — AES-256 key for medical records |
FRONTEND_URL |
✅ (prod) | Allowed CORS origin(s) — comma-separated for multi-env |
TWILIO_* |
✅ | SMS OTP delivery |
EMAIL_* |
✅ | Email OTP delivery |
CLOUDINARY_* |
✅ | File storage |
GROQ_API_KEY |
optional | Groq LLaMA API key — get free at console.groq.com. If omitted, AI features return 503 and the app starts normally |
AI_FEATURE_TRIAGE |
optional | true/false — toggle symptom triage without redeploy (default: true) |
AI_FEATURE_SUMMARIZE |
optional | true/false — toggle record summarization without redeploy (default: true) |
AI_FEATURE_IMAGE_ANALYSIS |
optional | true/false — toggle image analysis independently (default: true) |
⚠️ Never changeENCRYPTION_KEYafter records are stored — existing records become permanently unreadable.
| Variable | Required | Description |
|---|---|---|
VITE_API_URL |
✅ | Backend API base URL (e.g. http://localhost:5000/api/v1) — validated at build time |
VITE_SOCKET_URL |
✅ | Socket.io server URL (e.g. http://localhost:5000) — validated at build time |
GitHub Actions workflow is configured at .github/workflows/ci.yml.
- Push to
main - Pull request targeting
main - Manual run (
workflow_dispatch)
-
Backend Lint and Test
- Starts MongoDB and Redis service containers
- Installs backend dependencies
- Runs
npm run lint - Runs
npm test -- --passWithNoTests
-
Frontend Build
- Installs frontend dependencies
- Runs
npm run build
npm --prefix backend run lint
npm --prefix backend test -- --passWithNoTests
npm --prefix frontend run buildRequire both checks before merge into main:
Backend Lint and TestFrontend Build
Use this as the standard release flow for this repository.
- Confirm required production env vars are set (
MONGODB_URI,REDIS_URL,JWT_ACCESS_SECRET,JWT_REFRESH_SECRET,JWT_MFA_SECRET,ENCRYPTION_KEY,FRONTEND_URL, and required provider keys) - Ensure branch protection on
mainrequires CI checks - Ensure CI workflow is green on the release PR
# CI parity
npm --prefix backend run lint
npm --prefix backend test -- --passWithNoTests
npm --prefix frontend run build
# Container smoke test
docker-compose up -d --build
docker-compose psHealth checks:
- Frontend:
http://localhost/health - Backend:
http://localhost:5000/health
- Open PR to
main - Wait for both CI jobs to pass:
Backend Lint and TestFrontend Build
- Merge PR
- Deploy using your target environment process (container platform, VM, or orchestration)
- Verify API health endpoint returns healthy status
- Verify frontend loads and authentication flow works
- Verify one core end-to-end flow (e.g. registration/login or queue join)
- Check logs for startup/runtime errors
- If health checks fail or critical paths break:
- rollback to last known good image/commit
- re-run health checks
- investigate using CI logs + runtime logs before re-release
├── backend/src/
│ ├── server.js # Entry: helmet→compression→cors→JSON→morgan → routes → Socket.io
│ ├── config/
│ │ ├── database.js # Mongoose connection
│ │ ├── redis.js # ioredis (lazyConnect: true)
│ │ ├── validateEnv.js # Startup env validation — crashes fast if required vars missing
│ │ └── cloudinary.js # Cloudinary SDK
│ ├── controllers/ # One controller per domain
│ ├── middleware/
│ │ ├── auth.js # protect() + authorize(...roles) — Redis-cached user lookup
│ │ ├── auditLogger.js # Wraps admin routes — writes AuditLog entries
│ │ └── errorHandler.js # Global — masks 5xx messages in production
│ ├── models/ # 9 Mongoose schemas
│ ├── routes/ # Express routers (all at /api/v1/)
│ ├── services/ # notificationService, emailService, encryption.service
│ └── utils/
│ ├── userCache.js # getCachedUser(), invalidateUserCache(), getOrSetCache()
│ ├── rateLimiter.js # createRateLimiter() — Redis-backed factory
│ ├── logger.js # Winston — stdout always; file transports dev-only
│ ├── jwt.js # signAccessToken(), signRefreshToken(), verifyAccessToken()
│ └── pagination.js # parsePagination(query, defaultLimit)
│
└── frontend/src/
├── App.jsx # React Router v6 with ProtectedRoute role guards
├── store/
│ ├── useAuthStore.js # Zustand — tokens in memory only, non-sensitive fields persisted
│ └── notificationStore.js
├── services/
│ └── api.js # Axios instance + auto token-refresh interceptor
├── pages/
│ ├── patient/ # Dashboard, Queue, Appointments, HealthVault, Consent, Prescriptions
│ ├── doctor/ # Dashboard, QueueManagement, PatientRecords, PrescriptionsList, SharedRecords
│ └── admin/ # Dashboard, UserManagement, AuditLogs, Analytics, EmergencyReview
└── components/ # Shared UI: layouts, navigation, NotificationBell
MediQueue integrates Groq LLaMA 3.1 for two advisory features. The design follows a strict human-in-the-loop model.
| Principle | Implementation |
|---|---|
| AI is never a hard dependency | Every AI call is try/catch wrapped. Queue join and record view work with Groq completely down or rate-limited |
| Raw medical text never leaves the platform unredacted | A PII redaction pass strips names, emails, phone numbers, and IDs before any text is sent to Groq |
| Final decision always belongs to the user | The backend never reads AI output to set queue priority — it only stores the AI suggestion for audit purposes |
| Every AI action is auditable | AuditLog entries written for every summarize call: who, which record, model, latency, success/fail |
| Features can be toggled independently | AI_FEATURE_TRIAGE, AI_FEATURE_SUMMARIZE, AI_FEATURE_IMAGE_ANALYSIS env flags — no redeploy needed |
| Prompt versioning | Every AI call carries a promptVersion field (e.g. triage-v1, summary-v1) for future auditability |
- Patient types symptoms in the Join Queue form
- "Suggest priority" button calls
POST /api/v1/queue/triage(rate-limited: 5 req/min) - Groq returns
{ priority, reason, confidence }— pre-fills the priority selector - Patient can override — if they do,
aiOverridden: trueis stored on the queue entry - Non-dismissable disclaimer always shown: "AI suggests a priority level based on symptoms. This is not a medical diagnosis. A doctor will confirm."
- Patient (or doctor with consent) opens a record and clicks "Summarize with AI"
- Backend fetches the file from Cloudinary via server-side signed URL
- PDF path:
pdf-parseextracts text → PII stripped → sent tollama-3.1-8b-instant - Image path: file base64-encoded server-side → sent to
meta-llama/llama-4-scout-17b-16e-instruct(multimodal) - Response:
{ summary, keyFindings, followUpNeeded, transcriptionConfidence }— displayed in UI, never stored - Per-user quota: max 10 requests/hour (Redis counter, shared across PDF + image)
- Non-dismissable disclaimer: "AI-generated summary. Always consult your doctor for medical advice."
| Value | Meaning | UI behaviour |
|---|---|---|
high |
Text printed or clearly legible | No additional warning |
medium |
Some words unclear, overall meaning confident | Amber note: "Review key findings against your original document" |
low |
Significant portions illegible | Red warning: "Handwriting was difficult to read. Key details may be incomplete" |
GROQ_API_KEYmissing → app starts normally, AI endpoints return503with a clear message- Groq timeout (8 s) → one automatic retry, then fallback response
AI_FEATURE_*=false→ endpoint returns503, no Groq call is made
This project is proprietary and confidential.


