05-tech-stack-justification.md

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.js with baseURL from 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, update with 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. $transaction API 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.
  • 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: bcryptjs is a pure JS implementation — slower than native bcrypt module 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 io variable — 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

TechnologyAlternatives ConsideredWhy ChosenTrade-offs
React 19Vue, Next.js, vanilla JSComponent model for dashboard UI, hooks for stateNo SSR, large bundle
ViteCRA, WebpackFast dev server, zero-configNone significant
Tailwind CSSCSS Modules, styled-componentsRapid utility-first stylingVerbose JSX, less readable
Express 4Fastify, Nest.jsSimple, well-known, minimal setupNo structure enforcement, no validation
Prisma 7Drizzle, Knex, raw SQLSchema-first, auto-migrations, readable queriesCan't express partial indexes, perf overhead
PostgreSQL (Neon)MongoDB, MySQL, SupabaseRelational model fits, partial unique indexesExtra latency via Accelerate proxy
JWTSessions, Auth0, Supabase AuthStateless, serverless-compatibleNo revocation, no refresh, token bloat
Socket.IOWebSocket, Pusher, AblyRoom model for chat, easy broadcastBreaks on serverless, global state
node-cronBullMQ, Vercel CronSimple scheduling, in-processDies with process, can't run on serverless
Nodemailer + GmailSendGrid, AWS SESFree, quick setup500/day limit, no deliverability tracking
csv-parserDB import, external APIQuick solution for medicine search32MB re-parsed per request
bcryptjsargon2, scryptIndustry standard, portableSlower than native bcrypt