Resume and Interview — How to Present This Project
Resume Bullet Points
Built a full-stack patient management system using React 19, Node.js/Express, PostgreSQL, and Socket.IO, enabling doctors to manage prescriptions, track medication adherence, and communicate with patients in real-time.
Designed a concurrency-safe appointment booking system using PostgreSQL partial unique indexes, preventing double-booking under concurrent requests — validated with a 50-request concurrent booking test achieving exactly 1 success and 49 rejections.
Implemented an automated medication adherence pipeline with cron-based email reminders (3x daily), real-time WebSocket notifications, and time-window-based missed-dose detection, tracking per-patient adherence rates across morning/afternoon/evening schedules.
Engineered a two-phase patient onboarding flow where doctors invite patients via email with JWT-based password-setup tokens, creating a secure account activation pipeline with purpose-scoped, time-limited tokens.
Built role-based access control across 25+ API endpoints with JWT authentication, role-scoped authorization (doctor vs. patient), and ownership verification — including doctor-patient relationship validation before data access.
Elevator Pitch (30 seconds)
"I built a patient tracker system — it's a full-stack web app where doctors manage patients, write prescriptions, and schedule appointments, while patients track their daily medications and get automated email reminders. The interesting technical parts are the appointment booking system, which uses a PostgreSQL partial unique index to prevent double-booking even under concurrent requests, and the medication tracking pipeline, which automatically generates daily schedules from active prescriptions and uses cron jobs to email reminders and mark missed doses. The stack is React, Node.js, PostgreSQL with Prisma, and Socket.IO for real-time chat."
Technical Deep-Dive Answer
Q: "Walk me through the architecture of your project."
"It's a classic three-tier architecture — React frontend, Express API server, and PostgreSQL database, with Prisma as the ORM.
The frontend is a React 19 SPA built with Vite. It has two main dashboard views — one for doctors and one for patients — rendered based on the JWT role. All API communication goes through a centralized Axios client, and I use Socket.IO client for real-time chat and medication update notifications.
The backend is Express 4 running on Node.js. Every protected route goes through a JWT authentication middleware that verifies the token and attaches the decoded user to the request. There's a global middleware pipeline that runs on every request — including an async medication reminder trigger and a missed-medication checker that marks overdue meds as missed.
The backend is organized into domain modules — auth, doctor, patient, appointment, and chat — each containing handler functions. No service layer, which I'd add if I rebuilt it.
For the database, I'm using PostgreSQL on Neon with Prisma Accelerate for connection pooling, which is necessary because the backend was designed for serverless deployment on Vercel. The schema has 10 models with a many-to-many relationship between doctors and patients, prescriptions with nested prescribed medicines, and a MedicineAdherence table that tracks daily medication status.
The most technically interesting part is the appointment concurrency guard — I use a PostgreSQL partial unique index on (doctorId, slotStart) WHERE status <> 'cancelled'. This means even if two patients try to book the same slot simultaneously, only one INSERT succeeds at the database level, and the other gets a unique constraint violation that I catch and return as a 409 conflict. I validated this with a concurrent booking test that fires 50 simultaneous requests.
For real-time features, Socket.IO handles chat (room-based messaging) and pushes medication-update notifications when the cron marks doses as missed. The cron system uses node-cron with three daily reminder cycles and a continuous every-minute checker."
Anticipated Interview Questions + Answers
Design & Architecture
Q: Why did you choose this architecture?
"I chose a monolithic Express server for simplicity — this is a single-domain application (healthcare management), so microservices would be premature. The three-tier architecture gives clear separation: React handles UI, Express handles business logic and routing, and PostgreSQL handles persistence. I used Prisma because its schema-first approach means the database schema is version-controlled and auto-migrated.
The one architectural decision I'd change is the lack of a service layer. Currently, route handlers contain validation, authorization, business logic, and database queries all in one function. A service layer would make the business logic testable independently of HTTP."
Q: How does data flow through the system?
"A typical request starts in the React frontend, which sends an HTTP request via Axios with a JWT in the Authorization header. Express receives it, runs the middleware pipeline — JSON parsing, CORS, async medication checks — then the authenticate middleware verifies the JWT and attaches the decoded user to the request.
The route handler validates the input, checks role-based authorization, calls Prisma for database operations, and returns a JSON response. For real-time features, Socket.IO runs alongside Express on the same HTTP server, broadcasting events to connected clients in specific chat rooms."
Q: How did you decide between monolith and microservices?
"Monolith, because the application has a single bounded context — healthcare patient management. All features (auth, prescriptions, appointments, chat) share the same user models and authentication. Splitting into microservices would add network latency, distributed transaction complexity, and deployment overhead without meaningful benefits at this scale.
If I needed to scale, the first split I'd make is extracting the cron worker into a separate service, because the medication reminder loop is the biggest performance bottleneck and shouldn't share connection pools with the API server."
Q: If you had to redesign this from scratch, what would you change?
"Three things:
- Router organization — I'd use Express Router to group routes by domain instead of importing 30+ individual handler functions into app.js.
- Service layer — Separate business logic from HTTP handlers so I can test prescription creation logic without simulating HTTP requests.
- Managed auth — I'd use Supabase Auth or Clerk instead of building JWT auth from scratch. The token refresh flow, password reset emails, and invite tokens are commodity features that don't justify custom code."
Database
Q: Why did you choose PostgreSQL? Why not MongoDB?
"The data is inherently relational — doctors have patients, patients have prescriptions, prescriptions have medicines, patients have appointments with doctors. These are all foreign key relationships that SQL handles natively.
The schema actually started with MongoDB in mind — you can see @map("_id") annotations in the Prisma schema where IDs were originally MongoDB ObjectIds. But once I needed the partial unique index for appointment concurrency, MongoDB wasn't an option — MongoDB doesn't support partial unique indexes with the same flexibility.
PostgreSQL also lets me use Prisma's nested create syntax for creating a prescription with multiple medicines in a single atomic operation, which maps naturally to the relational structure."
Q: How is your database schema designed?
"Ten models with three key relationships:
- Doctor ↔ Patient: Many-to-many via an implicit join table. A doctor can have many patients, a patient can have multiple doctors.
- Prescription → PrescribedMedicine: One-to-many. A prescription contains multiple medicines, each with timing (JSON field storing morning/afternoon/evening booleans), dosage, and duration.
- MedicineAdherence: The tracking table — one row per patient per medicine per time-of-day per day. This is the fastest-growing table and would need partitioning at scale.
IDs are split: patients use auto-incrementing integers, doctors use UUIDs. This was an early design inconsistency that required type coercion (parseInt() vs String()) throughout the codebase."
Q: What indexes do you have and why?
"Three explicit indexes:
Patient.emailunique — prevents duplicate accounts and enablesfindUnique()lookup.Doctor.emailunique — same purpose.Appointment(doctorId, slotStart) WHERE status <> 'cancelled'— partial unique index that prevents double-booking. This is the concurrency guard — even if two concurrent INSERT statements pass the application-level conflict check, the database rejects the second one.
Missing indexes I'd add: MedicineAdherence(patientId, scheduledDate) for the daily medication query, and Appointment(doctorId, appointmentDate) for the doctor's schedule view."
Q: How would your DB handle 1 million records? 100 million?
"At 1M records (mostly in MedicineAdherence — ~67 patients over a year), the current schema works with proper indexes. At 100M (6,700 patients over a year), the MedicineAdherence table becomes the bottleneck.
I'd partition by month — each month's data in a separate partition. Queries for today's medications only scan the current month's partition. Old data can be archived to cold storage. For the adherence stats aggregation, I'd add materialized views that precompute daily/weekly stats overnight, so the API doesn't recalculate on every request."
Scaling
Q: What's the bottleneck in your current system?
"The medication reminder cron at 8 AM. It queries every patient with prescriptions, runs 3-4 queries per patient, and sends an email for each. At 500 patients, that's 2,000+ queries in a single loop, all sharing the same 5-connection Prisma pool with the API server. During this window, API requests back up.
The fix is a dedicated worker process with a job queue — enqueue one job per patient, process with bounded concurrency, and use a separate database connection pool."
Q: How would you scale this to handle 10x the traffic?
"First, separate the three concerns into independent services: API server (stateless, horizontally scalable), WebSocket server (with Redis adapter for multi-instance), and cron worker (single instance with job queue).
Second, add the missing database indexes and implement Redis caching for frequently-read data (doctor's patient list, available appointment slots, medicine search results).
Third, move the medicine CSV into the database with full-text search — that eliminates the 32MB file parsing bottleneck."
Q: What happens if the database goes down?
"Everything fails. There's no caching layer, no circuit breaker, and no fallback. Prisma throws connection errors, Express catches them and returns 500s to the client.
To handle this properly, I'd add: health check endpoint that tests DB connectivity, Redis cache for recent reads (serve stale data during outages), circuit breaker pattern (stop hitting the DB when it's failing), and graceful degradation (show cached medication schedule even if the DB is down)."
Features
Q: Walk me through how appointment booking works end-to-end.
"Patient opens the appointment booking component, selects a doctor, picks a date. The frontend fetches available slots — the backend generates 8 hourly slots (9 AM to 5 PM) and subtracts any non-cancelled existing appointments. The patient selects a slot.
On booking: the frontend sends the doctor ID and normalized time. The backend first does a fast-path conflict check — a simple findFirst query to see if the slot is taken. If it passes, it attempts an INSERT. Here's the key part: even if two requests pass the fast-path check simultaneously (a race condition), the PostgreSQL partial unique index on (doctorId, slotStart) WHERE status <> 'cancelled' ensures only one INSERT succeeds. The second gets a P2002 unique constraint violation, which I catch and return as a 409 Conflict with 'This time slot is already booked'.
I verified this with a test script that fires 50 simultaneous booking requests at the same slot. Exactly 1 gets a 201, and 49 get 409s."
Q: How do you handle authentication and authorization?
"JWT-based authentication with role-based authorization. On login, the server generates a JWT containing the user's ID, email, name, and role, signed with a server-side secret, expiring in 24 hours.
Every protected route goes through an authenticate middleware that extracts the token from the Authorization header, verifies it, and attaches the decoded user to req.user. Route handlers then check req.user.role for role-based access — for example, only doctors can create prescriptions, only patients can book appointments.
Some endpoints go further and verify ownership — for example, the doctor prescriptions endpoint checks that the patient is actually assigned to the requesting doctor before returning data. Patients can only cancel their own appointments, while doctors can complete, cancel, or mark appointments as missed."
Q: How do you handle errors?
"Error handling is layered. The authentication middleware returns 401 for missing or invalid tokens. Route handlers validate inputs and return 400 for missing fields, 403 for role violations, 404 for not-found resources, and 409 for conflicts (like double-booking). Database errors from Prisma are caught in try-catch blocks and returned as 500s.
One area I'd improve: error response shapes are inconsistent — some handlers use { message }, others use { error }, and some expose internal error messages. I'd standardize with a custom error class and a global error handler middleware."
Coding & Best Practices
Q: How do you handle environment-specific configuration?
"Environment variables via dotenv. The .env file contains DATABASE_URL, JWT_SECRET, EMAIL_USER, EMAIL_PASS, PORT, and FRONTEND_URL. The frontend uses Vite's import.meta.env.VITE_API_URL for the API base URL.
One issue: dotenv.config() is called in multiple files (app.js, signin.js, jwtToken.js, scheduleTasks.js, etc.) because of ES module import ordering. In a refactor, I'd call it once at the entry point and ensure it's loaded before any module that reads env vars."
Q: How would you add feature X to this project?
"Say we want to add prescription PDF export. I'd:
- Add a
GET /doctor/prescriptions/:id/pdfendpoint - Use
pdfkitor@react-pdf/rendererto generate the PDF from the prescription data - Set
Content-Type: application/pdfand stream the response - On the frontend, add a download button that calls this endpoint with the auth token
Following the existing patterns: handler in doctor/, authenticate middleware, role check for doctor, fetch prescription with includes, generate PDF, return. The PDF generation itself is CPU-intensive — at scale, I'd queue it as a background job."
Metrics & Impact
Q: How many users/requests can this handle?
"In its current form, comfortably handling ~100 concurrent users with ~500ms average response time. The bottleneck is the medicine CSV parsing (32MB per search) and the medication reminder cron (N+1 queries). With the CSV imported to the DB and the cron optimized to batch queries, it could handle ~1,000 concurrent users on a single $20/month server.
The concurrent booking test proves the appointment system can handle burst traffic — 50 simultaneous requests resolve correctly in under 1 second."
Q: What monitoring do you have in place?
"Honestly, just console.log and console.error. For production, I'd add Sentry for error tracking, Prometheus + Grafana for metrics (response time, DB query time, email delivery rate), and structured logging with Winston or Pino."
Q: What would you build next?
"Three things in priority order:
- Rate limiting — the biggest security gap.
express-rate-limitfor login brute-force protection and API abuse prevention. - Notification preferences — patients should choose whether to receive email reminders, and at what times. Currently, everyone gets 8 AM/1 PM/8 PM with no opt-out.
- Admin dashboard — system-wide view of patient adherence rates, appointment utilization, and active prescriptions. The data is already there; it just needs an aggregation endpoint and a visualization."