Tech Stack Justification — Why I Used What I Used
Frontend
React 19
- What it is: Component-based UI library for building single-page applications.
- Why for THIS project: Two dashboards (doctor/patient) with shared components (appointment list, chat), dynamic state (medication tracking), and real-time updates — React's component model + hooks handle this well.
- Alternatives considered:
- Vue.js — simpler learning curve but smaller ecosystem for healthcare-specific UI libraries.
- Next.js — SSR would improve SEO, but this is a dashboard app behind authentication — SEO is irrelevant.
- Plain HTML/JS — would work for CRUD but the real-time medication tracker and chat UI would be painful without a component framework.
- What it handles that I'd build manually otherwise: Virtual DOM diffing for the medication tracker (frequent re-renders on status changes), component composition for shared UI (AppointmentList used by both roles).
- Limitations: No SSR — initial load is a blank page until JS hydrates. Large bundle size for a dashboard app.
- At 10x scale: Still React, but with Next.js for code-splitting and SSR on public pages, and React Query/TanStack Query for server state management instead of raw
useState+useEffect.
Vite (v6)
- What it is: Build tool and dev server with near-instant hot module replacement.
- Why for THIS project: Fast dev feedback loop. No Webpack config complexity.
- Alternatives: Create React App (deprecated), Webpack (slow, verbose config), Parcel (less control).
- Limitations: None significant for this project size.
Tailwind CSS v4
- What it is: Utility-first CSS framework.
- Why for THIS project: Rapid UI development without writing custom CSS. The dashboard has many small UI elements (cards, badges, buttons) that benefit from utility classes.
- Alternatives: CSS Modules (more boilerplate), styled-components (runtime CSS-in-JS overhead), plain CSS (too slow for iterating on dashboards).
- Limitations: Large number of utility classes in JSX makes components harder to read (see
DoctorDashboard.jsx— 823 lines of mixed logic and Tailwind).
shadcn/ui (Radix UI primitives)
- What it is: Copy-paste component library built on Radix UI headless components.
- Why for THIS project: Needed accessible dialogs (AlertDialog for delete confirmation), scroll areas, and styled buttons without building from scratch.
- Alternatives: Material UI (heavier, opinionated), Headless UI (fewer components), Ant Design (more enterprise-oriented).
- Limitations: Small set of components used (
Button,Card,AlertDialog). Could have done without it.
Axios
- What it is: HTTP client with interceptors, automatic JSON parsing, and error handling.
- Why for THIS project: Centralized
apiClient.jswithbaseURLfrom env vars. Interceptors could be added for auto-attaching auth tokens (not currently done — tokens are manually added per request). - Alternatives: Native
fetch()— would work fine, but Axios provides better error handling and request/response transformation. - At 10x scale: Replace raw Axios calls with React Query for caching, deduplication, and automatic refetching.
Socket.IO Client
- What it is: Real-time bidirectional event-based communication.
- Why for THIS project: Chat requires real-time message delivery. Medication status updates pushed to patients after cron marks meds as missed.
- Alternatives: Native WebSocket API (no auto-reconnect, no room management), Server-Sent Events (one-way only — insufficient for chat).
- Limitations: Socket.IO doesn't work on Vercel serverless. In production, this feature silently breaks.
Backend
Node.js + Express 4
- What it is: JavaScript runtime + minimalist web framework.
- Why for THIS project: Same language as frontend (JavaScript everywhere), large middleware ecosystem, easy to scaffold REST APIs. Express is the most straightforward choice for a REST API with route handlers.
- Alternatives:
- Fastify — faster, schema-based validation built-in. Would have been better for input validation.
- Nest.js — structured with decorators, dependency injection. Overkill for this project's size but would enforce better architecture.
- Django/Flask — Python alternatives. Would work but introduces a language boundary.
- Limitations: No built-in structure — led to the "all routes in app.js" anti-pattern. No built-in validation — led to manual
if (!field)checks everywhere. - At 10x scale: Migrate to Fastify with schema validation, or Nest.js for enforced architecture (services, controllers, DTOs).
Prisma ORM (v7.5)
- What it is: Type-safe ORM with schema-first database design and auto-generated query builder.
- Why for THIS project: Schema file is the single source of truth for the database. Migrations are auto-generated. Query syntax is readable (
findMany,create,updatewith includes and nested writes). - Alternatives:
- Drizzle ORM — lighter weight, closer to SQL. Better for complex queries.
- Knex.js — query builder without schema management. More control but more boilerplate.
- Raw SQL — maximum control but no migration management or type safety.
- What it handles: Migration generation, connection pooling (via Accelerate), nested creates (prescription + medicines in one call), relation includes.
- Limitations: Can't express partial unique indexes — required raw SQL migration. No built-in soft delete.
$transactionAPI is limited compared to raw SQL transactions. - At 10x scale: Still Prisma, but with Drizzle for analytics queries. Or move heavy read paths to raw SQL.
Prisma Accelerate
- What it is: Prisma's connection pooling proxy for serverless environments.
- Why for THIS project: Vercel serverless functions can't maintain persistent database connections. Accelerate pools connections through a proxy.
- Alternatives: PgBouncer (self-hosted), Neon's built-in connection pooling.
- Limitations: Adds ~5-15ms latency per query. Extra dependency and cost.
PostgreSQL (Neon)
- What it is: Relational database hosted on Neon's serverless Postgres platform.
- Why for THIS project: Relational data model (doctors have patients, patients have prescriptions, prescriptions have medicines). The partial unique index for appointment concurrency is a PostgreSQL-specific feature.
- Alternatives:
- MongoDB — schema was originally designed with MongoDB in mind (the
@map("_id")annotations are remnants). But relational queries (doctor → patients → prescriptions → medicines) are far more natural in SQL. - MySQL — would work but doesn't support partial unique indexes (would need a different concurrency strategy).
- Supabase — Postgres + auth + realtime built-in. Would have eliminated the need for custom JWT auth and Socket.IO.
- MongoDB — schema was originally designed with MongoDB in mind (the
- At 10x scale: Still PostgreSQL, but with read replicas for the medication adherence query load. Consider partitioning MedicineAdherence by month.
JSON Web Tokens (jsonwebtoken)
- What it is: Stateless authentication tokens.
- Why for THIS project: Serverless-compatible (no session store needed). Token contains role + user data, so auth middleware doesn't hit the DB.
- Alternatives:
- Session cookies — simpler but requires a session store (Redis/DB). Incompatible with serverless without external state.
- Auth0/Clerk/Supabase Auth — managed auth. Would have saved building signup/signin/reset/invite flows but adds vendor lock-in.
- Limitations: No token revocation (can't force logout). No refresh tokens (user re-logins every 24h). Token bloat (entire user profile in payload).
bcryptjs
- What it is: Pure JavaScript bcrypt implementation for password hashing.
- Why for THIS project: Industry-standard password hashing. 10 salt rounds provide good security/speed balance.
- Alternatives:
argon2(more secure, recommended for new projects),scrypt(built into Node.js). - Limitations:
bcryptjsis a pure JS implementation — slower than nativebcryptmodule but more portable.
Nodemailer
- What it is: Email sending library for Node.js.
- Why for THIS project: Three email types: patient invites, password resets, medication reminders. Gmail SMTP as transport.
- Alternatives: SendGrid, Mailgun, AWS SES — all more production-ready with deliverability guarantees, bounce handling, and analytics.
- Limitations: Gmail SMTP has daily sending limits (~500/day). No email templates — HTML is hardcoded strings. No delivery tracking.
- At 10x scale: Replace with SendGrid or AWS SES. Add email templates via MJML or React Email.
node-cron
- What it is: Cron job scheduler for Node.js.
- Why for THIS project: Medication reminders need to fire at specific times (8 AM, 1 PM, 8 PM). Missed medication checks at defined intervals.
- Alternatives: Bull/BullMQ (Redis-backed job queue), agenda.js (MongoDB-backed), external cron (Vercel Cron, AWS EventBridge).
- Limitations: Runs in-process only — if the process dies, cron dies. On Vercel serverless, cron never runs. The "reminder on every HTTP request" middleware is a workaround.
- At 10x scale: Replace with a dedicated job queue (BullMQ + Redis) or platform-native cron (Vercel Cron Functions).
Socket.IO
- What it is: Real-time bidirectional communication library.
- Why for THIS project: Chat requires real-time message delivery. Room-based model (join chat room → receive messages) maps directly to the chat data model.
- Alternatives: Plain WebSocket (no room management), Pusher/Ably (managed, scalable), Supabase Realtime (built-in with DB triggers).
- Limitations: Incompatible with Vercel serverless — requires persistent connections. Uses global
iovariable — not testable. No namespacing for different event types. - At 10x scale: Replace with a managed service (Pusher, Ably) or deploy to a long-running server (Railway, Render).
csv-parser
- What it is: Streaming CSV parser for Node.js.
- Why for THIS project: Parses the 32MB Indian medicine dataset for prescription autocomplete.
- Alternatives: Import CSV into PostgreSQL (better query performance), use a medicine API (external dependency).
- Limitations: Re-parses entire 32MB file on every request. No caching. No indexing.
- At 10x scale: Import into PostgreSQL with full-text search index. Or use Elasticsearch for fuzzy medicine name matching.
Tech Stack Decision Matrix
| Technology | Alternatives Considered | Why Chosen | Trade-offs |
|---|---|---|---|
| React 19 | Vue, Next.js, vanilla JS | Component model for dashboard UI, hooks for state | No SSR, large bundle |
| Vite | CRA, Webpack | Fast dev server, zero-config | None significant |
| Tailwind CSS | CSS Modules, styled-components | Rapid utility-first styling | Verbose JSX, less readable |
| Express 4 | Fastify, Nest.js | Simple, well-known, minimal setup | No structure enforcement, no validation |
| Prisma 7 | Drizzle, Knex, raw SQL | Schema-first, auto-migrations, readable queries | Can't express partial indexes, perf overhead |
| PostgreSQL (Neon) | MongoDB, MySQL, Supabase | Relational model fits, partial unique indexes | Extra latency via Accelerate proxy |
| JWT | Sessions, Auth0, Supabase Auth | Stateless, serverless-compatible | No revocation, no refresh, token bloat |
| Socket.IO | WebSocket, Pusher, Ably | Room model for chat, easy broadcast | Breaks on serverless, global state |
| node-cron | BullMQ, Vercel Cron | Simple scheduling, in-process | Dies with process, can't run on serverless |
| Nodemailer + Gmail | SendGrid, AWS SES | Free, quick setup | 500/day limit, no deliverability tracking |
| csv-parser | DB import, external API | Quick solution for medicine search | 32MB re-parsed per request |
| bcryptjs | argon2, scrypt | Industry standard, portable | Slower than native bcrypt |