Skip to content

About

Front desk to consultation to billing for small clinics - multi-tenant, role-based, with AI patient insights. Every API route is permission-checked and tenant-scoped before it touches data. Next.js App Router, Prisma, MongoDB, JWT, rate limiting.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

MediFlow Clinic Management System

MediFlow screenshot

MediFlow is a role-based clinic operations platform built with Next.js App Router, TypeScript, Prisma, MongoDB, Tailwind CSS, and shadcn/ui. It covers the front-desk to consultation to billing workflow for small clinics and multi-user teams.

This README is a current-state guide based on the checked-in codebase as of March 9, 2026.

Table of Contents

Overview

MediFlow is designed for three runtime roles:

  • admin
  • doctor
  • receptionist

The application currently supports:

  • Patient registration and profile management
  • Appointment booking and queue transitions
  • Clinical encounters and prescription authoring
  • Billing, checkout, payment collection, voids, and refunds
  • Reports and operational dashboards
  • User and service management for admins

What the App Does

Reception and Front Desk

  • Register new patients
  • Search and update patient records
  • Schedule appointments
  • Move patients into queue
  • Create bills and collect payments

Doctor Workflow

  • View queue and daily appointments
  • Start consultations
  • Record encounters
  • Create prescriptions
  • Complete appointments and hand off to billing
  • Review reports and patient history

Admin Workflow

  • Everything receptionist and doctor roles can do, plus:
  • Create the initial admin account through /makeadmin
  • Manage users
  • Manage services
  • Access settings and reports

Roles and Access

Receptionist

  • /dashboard
  • /patients
  • /add-patient
  • /appointments
  • /queue
  • /billing

Doctor

  • /dashboard
  • /appointments
  • /queue
  • /patients
  • /prescriptions
  • /billing
  • /reports

Admin

  • /dashboard
  • /appointments
  • /queue
  • /patients
  • /add-patient
  • /billing
  • /reports
  • /settings
  • /users
  • /prescriptions

Notes:

  • Route access is enforced in both middleware.ts and the client auth context.
  • Doctors can access the billing screen, but billing write operations are still permission-restricted at the API layer.

End-to-End Workflow

  1. User signs in at /.
  2. Session cookie is issued after successful login.
  3. Front desk registers a patient or reuses an existing patient record.
  4. Appointment is created and assigned to a doctor.
  5. Patient is moved through queue states such as Scheduled, Arrived, InQueue, InProgress, and Completed.
  6. Doctor performs consultation and records encounter details.
  7. Doctor creates a prescription, or explicitly completes without one where allowed by the workflow.
  8. Billing creates or finalizes a bill from the encounter or appointment.
  9. Payment is collected and tracked.
  10. Admins and doctors review operational reports.

Tech Stack

  • Next.js 15.2.4 with App Router
  • React 19
  • TypeScript 5
  • Prisma 5 with MongoDB datasource
  • Tailwind CSS 3
  • shadcn/ui and Radix UI primitives
  • jose for JWT session handling
  • bcryptjs for password hashing
  • Recharts for reporting visuals

Project Structure

app/
  (dashboard)/            Protected dashboard routes
  api/                    Route handlers for auth, patients, billing, etc.
  layout.tsx              Root layout
  page.tsx                Login page
  makeadmin/              First-admin bootstrap flow

components/
  appointments/           Appointment UI
  auth/                   Login and auth-related UI
  billing/                Billing UI
  dashboard/              Overview widgets
  doctor/                 Queue and prescription flows
  layout/                 Sidebar, theme toggle, shared shell
  patients/               Patient forms and profile views
  reports/                Reporting UI
  settings/               Settings screens
  users/                  User management UI
  ui/                     Shared design system components

config/                   Brand, roles, routes, theme, and feature config
contexts/                 Auth context used by client-side UI
docs/                     Internal project docs and audits
lib/                      Auth, DB, RBAC, API client, realtime helpers
prisma/                   Prisma schema and seed
public/                   Static assets
services/                 Supporting service-layer code
types/                    Shared TypeScript types

Getting Started

Prerequisites

  • Node.js 20 LTS recommended
  • npm
  • A running MongoDB database or MongoDB Atlas cluster

Next.js 15 supports Node 18.18+, but Node 20 is the safer default for local development.

Installation

npm install

Configure Environment

Create or update .env.local with the required keys listed below.

Start the App

npm run dev

Open http://localhost:3000.

Environment Variables

The repository currently reads these environment variables:

Variable Required Purpose
DATABASE_URL Yes MongoDB connection string used by Prisma
JWT_SECRET_KEY Yes Primary JWT signing secret for session cookies
NEXTAUTH_SECRET Optional Fallback JWT secret if JWT_SECRET_KEY is not set
ADMIN_BOOTSTRAP_TOKEN Recommended Required to create the first admin through /makeadmin
COOKIE_SECURE Recommended Set to true in HTTPS environments so auth cookies are marked secure
NEXT_PUBLIC_APP_URL Recommended Used in middleware for allowed CORS origins
MOCK_LOGIN Optional Present in env files; legacy/debug flag
NEXT_PUBLIC_MOCK_LOGIN Optional Present in env files; legacy/debug flag
NEXT_PUBLIC_BRAND_LOGO_SRC Optional Overrides logo source from config/brand.ts

Example:

DATABASE_URL="mongodb+srv://<user>:<password>@<cluster>/<db>?retryWrites=true&w=majority"
JWT_SECRET_KEY="replace-with-a-long-random-secret"
ADMIN_BOOTSTRAP_TOKEN="replace-with-bootstrap-token"
COOKIE_SECURE="false"
NEXT_PUBLIC_APP_URL="http://localhost:3000"

Database Setup

The Prisma datasource is configured for MongoDB in prisma/schema.prisma.

Generate Prisma client and sync the schema:

npx prisma generate
npx prisma db push

Seed the database:

npx prisma db seed

What the seed creates:

  • 1 admin
  • 2 doctors
  • 1 receptionist
  • Sample patients
  • Active services
  • Sample appointments
  • At least one encounter and prescription
  • Sample billing data
  • Audit log entries

Seed Data and Demo Credentials

Seeded credentials from prisma/seed.ts:

Role Email Password
Admin admin@mediflow.com Admin@123
Doctor doctor@mediflow.com Doctor@123
Doctor doctor2@mediflow.com Doctor@123
Receptionist reception@mediflow.com Reception@123

If you do not seed the database, create the first admin at /makeadmin using ADMIN_BOOTSTRAP_TOKEN.

Available Scripts

Defined in package.json:

Script Command Purpose
npm run dev next dev Start local development server
npm run build next build Create production build
npm run start next start Run production server
npm run lint next lint Run linting
npm run typecheck tsc --noEmit Type-check without emitting
npm run check tenancy + error checks Release gate. Runs both self-checks below
npm run check:tenancy scripts/check-tenant-scope.ts Proves clinic isolation rules hold. No database needed
npm run check:errors scripts/check-error-disclosure.ts Proves API errors do not leak internal detail. No database needed
npm run check:api scripts/check-api-isolation.ts Integration check against a running app: cross-tenant access and the billing path
npm run e2e scripts/e2e-smoke.ts Launch gate. Walks the whole clinic journey over HTTP against a running app
npm run db:backfill prisma/backfill-clinic-id.ts One-shot clinicId backfill for pre-multi-tenant databases
npm run clean rm -rf .next .next-dev Remove Next.js build artifacts

check:tenancy and check:errors need no database and run in CI on every push. check:api needs the app running and a reachable database:

npm run dev      # terminal 1
npm run check:api # terminal 2

npm run e2e is the end-to-end smoke test. It signs in as all three roles and walks the real journey — register a patient, book, queue, consult, prescribe, complete, raise the bill, take a part payment, settle the balance — asserting the RBAC and money rules along the way. Point it at any environment:

BASE_URL=http://localhost:3000 npm run e2e

It expects the seeded accounts from prisma/seed.ts, creates its own patient with a unique phone, and is safe to re-run (it waits out the login throttle rather than asking for it to be relaxed).

Application Routes

Public

  • / login page
  • /makeadmin first-admin bootstrap page
  • /reset-password password reset (reached via an admin-issued link)

Dashboard Pages

  • /dashboard
  • /patients
  • /patients/[id]
  • /add-patient
  • /appointments
  • /queue
  • /prescriptions
  • /billing
  • /reports
  • /settings
  • /users

API Overview

Authentication

  • POST /api/auth/login
  • POST /api/auth/logout
  • GET /api/auth/me
  • POST /api/auth/register
  • POST /api/auth/makeadmin
  • GET /api/auth/doctors

Patients

  • GET /api/patients
  • POST /api/patients
  • GET /api/patients/:id
  • PATCH /api/patients/:id

Appointments

  • GET /api/appointments
  • POST /api/appointments
  • PATCH /api/appointments
  • GET /api/appointments/:id
  • PATCH /api/appointments/:id
  • DELETE /api/appointments/:id

Encounters

  • GET /api/encounters
  • POST /api/encounters
  • GET /api/encounters/:id
  • PATCH /api/encounters/:id

Prescriptions

  • GET /api/prescriptions
  • POST /api/prescriptions
  • GET /api/prescriptions/:id
  • PATCH /api/prescriptions/:id

Billing and Payments

  • GET /api/billing
  • POST /api/billing
  • GET /api/billing/:id
  • PATCH /api/billing/:id
  • POST /api/billing/checkout
  • POST /api/billing/:id/void
  • POST /api/payments/:id/refund

Services and Users

  • GET /api/services
  • POST /api/services
  • PATCH /api/services/:id
  • GET /api/users
  • PATCH /api/users/:id

Dashboard and Realtime

  • GET /api/dashboard/stats
  • GET /api/realtime/appointments

Data Model

Primary Prisma models:

  • User
  • Patient
  • Appointment
  • AppointmentStatusTransition
  • Encounter
  • Prescription
  • PrescriptionItem
  • Bill
  • BillItem
  • AuditLog
  • Service

Key relationships:

  • A patient can have many appointments, encounters, prescriptions, and bills.
  • An appointment belongs to one patient and one doctor.
  • An encounter links appointment, patient, and doctor.
  • A prescription links encounter, patient, and doctor.
  • A bill belongs to a patient and can optionally link to an encounter.

Authentication and Security

The current auth stack is cookie-based and JWT-backed.

  • Session cookie name: session
  • Token library: jose
  • Password hashing: bcryptjs
  • Session lifetime: 24 hours
  • Route enforcement: middleware.ts
  • API authorization: lib/rbac.ts

Implemented security behavior:

  • Protected dashboard routes by role
  • API permission checks by role and action
  • Rate limiting on sensitive auth endpoints
  • Security headers on middleware responses
  • Request ID propagation with x-request-id
  • Audit logging for important actions

Current rate-limit config in middleware:

  • /api/auth/login: 5 requests per minute
  • /api/auth/register: 3 requests per minute
  • /api/auth/makeadmin: 5 requests per minute

Realtime Behavior

Appointments are updated through a combination of:

  • Server-Sent Events on /api/realtime/appointments
  • In-memory realtime event fan-out in the backend
  • Polling and retry behavior in the client API layer

This supports queue refreshes and status updates without relying only on manual reloads.

Multi-Tenancy

Every record that belongs to a clinic carries a clinicId, and isolation is enforced centrally rather than per query:

  • requireAuth (lib/rbac.ts) resolves the caller's clinic and binds it to the request via AsyncLocalStorage (lib/tenant-context.ts).
  • A Prisma client extension (lib/db.ts) injects clinicId into the where of every read, update and delete on a tenant model, and stamps it onto every create. Routes cannot forget to scope a query.
  • It fails closed: touching a tenant model with no clinic in scope throws rather than returning unscoped rows.
  • Genuinely cross-tenant work (login by email, admin bootstrap) must opt out explicitly with withoutTenantScope().

npm run check proves these rules hold and is a release gate.

Two limits worth knowing: a user belongs to exactly one clinic, and there is no platform-level super-admin that can see across clinics.

Upgrading an Existing Database (breaking)

clinicId is a required field, so documents written before multi-tenancy will fail to read until they are backfilled. On an existing deployment, run this once before starting the new build:

npm run db:backfill -- --dry-run     # report what would change
npm run db:backfill                  # apply; defaults to clinic_1
CLINIC_ID=acme npm run db:backfill   # or name the clinic explicitly

The script is idempotent and only touches documents with no clinicId. It also drops the old global unique index on patients.phone — that constraint is now per clinic (@@unique([clinicId, phone])), so two clinics can each register the same phone number. Run npx prisma db push afterwards to create the new index.

Deployment Notes

Before deploying:

  1. Set production values for DATABASE_URL, JWT_SECRET_KEY, and NEXT_PUBLIC_APP_URL.
  2. Run npx prisma generate.
  3. Run npx prisma db push against the target database.
  4. Existing databases only: run npm run db:backfill (see above).
  5. Run npm run check — tenant isolation and error-disclosure gates.
  6. Build the app with npm run build.
  7. Start it with npm run start.
  8. Run BASE_URL=<deployed-url> npm run e2e against the running deployment. This is the last gate: it proves the whole reception → consultation → billing path actually works in that environment, not just that it compiled.

Recommended production checks:

  • Serve over HTTPS. Session cookies are Secure by default; COOKIE_SECURE=false exists only for local http development and must never be set in production.
  • Rotate bootstrap and JWT secrets
  • Restrict MongoDB network access
  • Seed only in non-production or controlled environments
  • Verify CORS origin matches the deployed app URL

Test-credential autofill

The login page shows an autofill panel for the seeded E2E accounts. It is gated on NODE_ENV !== "production", and the credentials sit inside that conditional so the bundler drops them entirely from a production build — a render-only guard would still have shipped the passwords in the JS bundle. To confirm after a build:

grep -r "AdminE2E123" .next/static .next/server   # must return nothing

Rate limiting

Auth endpoints are throttled using a MongoDB-backed counter (RateLimitCounter), so limits survive serverless cold starts and are shared across instances. This runs in the API routes, not middleware: middleware executes on the edge runtime, where Prisma cannot connect. Login is limited per IP and per email address. If the database is unreachable the limiter fails open and logs ratelimit.unavailable rather than locking everyone out.

Password reset

No mail transport is configured, so reset is admin-initiated: Users → ⋯ → Create password reset link produces a single-use link (valid one hour) for the admin to hand to the staff member. Only the SHA-256 hash of the token is stored. Completing a reset sets passwordChangedAt, which invalidates every session issued earlier — so a reset evicts an attacker who already holds a cookie.

Adding self-service "forgot password" needs only a mailer plus a route that looks the user up by email; the token machinery already exists in lib/password-reset.ts.

Known Constraints

These are current implementation constraints, not README omissions:

  • Page routes are still gated client-side through AuthContext; the API is the real enforcement boundary. A direct URL may flash UI before redirecting, but no data is served without a valid session.
  • A user belongs to exactly one clinic, and there is no cross-clinic super-admin role.
  • There is no subscription, licensing, or tenant self-provisioning layer — clinics are created by an operator running the admin bootstrap.
  • Healthcare compliance (DPDP/HIPAA posture, retention policy, patient data export, documented backup and restore) is not addressed in the codebase and must be handled before selling into regulated use.
  • Doctors can open the billing route, but billing write actions are still blocked by API permissions.
  • Admins can access prescriptions directly, but the admin sidebar does not expose that route.
  • The add-patient UI captures more fields than are fully persisted end-to-end in all flows.
  • Some configuration and feature-flag scaffolding exists but is not yet fully wired into runtime behavior.
  • A public /register page does not exist even though registration capability exists at the API level for authorized users.

Troubleshooting

Unauthorized - Please login

  • Check that JWT_SECRET_KEY is set.
  • Confirm cookies are enabled in the browser.
  • If running over HTTP locally, use COOKIE_SECURE=false.

First admin cannot be created

  • Confirm ADMIN_BOOTSTRAP_TOKEN is set in your environment.
  • Use the same token on /makeadmin.
  • After at least one active admin exists, bootstrap is no longer public.

Prisma cannot connect to MongoDB

  • Verify DATABASE_URL.
  • Confirm the database allows inbound connections from your machine or host.
  • Re-run npx prisma generate and npx prisma db push.

Login works but dashboard redirects unexpectedly

Appointment or billing actions fail

  • Confirm the signed-in role has the required backend permission in lib/rbac.ts.
  • Review server logs for validation failures and request IDs.

Additional Internal References

About

Front desk to consultation to billing for small clinics - multi-tenant, role-based, with AI patient insights. Every API route is permission-checked and tenant-scoped before it touches data. Next.js App Router, Prisma, MongoDB, JWT, rate limiting.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages