Skip to content

New reading notes on system design, software architecture, and AI engineering. Explore reading

← Projects

Jul 31, 2026 · 8 min read · 0 views

The Care Nexus

AI-assisted healthcare SaaS platform

The Care Nexus is an AI-powered, voice-enabled healthcare management platform designed to digitize and centralize clinical workflows for independent doctors, private clinics, and patients. The Care Nexus follows a hybrid SaaS-based architecture, supporting both standalone doctors and multi-doctor clinics, making it scalable, modular, and suitable for real-world healthcare environments.

Timeline
2026-03 - 2026-07
Origin
Original — own idea, self-directed
Role
Final year project, team of six. I wrote the software.

Overview

The Care Nexus is an AI-powered, voice-enabled healthcare management platform designed to digitize and centralize clinical workflows for independent doctors, private clinics, and patients.

The system addresses critical healthcare challenges prevalent in Pakistan and similar developing regions, including fragmented medical records, handwritten prescriptions, lack of clinic-level analytics, inefficient appointment handling, and poor patient follow-up mechanisms.

By leveraging modern technologies such as Next.js, Node.js, MongoDB, Redis, Whisper API, and OpenAI/Gemini, the platform enables doctors to generate prescriptions using voice, clinics to monitor operational and performance analytics in real time, and patients to maintain lifelong digital medical histories with controlled access.

The Care Nexus follows a hybrid SaaS-based architecture, supporting both standalone doctors and multi-doctor clinics, making it scalable, modular, and suitable for real-world healthcare environments.

Problem

Problems to solve

  • Paper-based documentation

    Doctors spend consultation time writing prescriptions and notes by hand instead of keeping their attention on the patient.

  • Fragmented medical history

    Patient history can remain in paper records that may not be available at the next visit.

  • Limited clinic visibility

    Without an operational view, clinic staff lack a clear basis for understanding appointments, staffing, and revenue.

  • Disconnected follow-up

    After a visit, follow-up depends on the patient remembering rather than on a continuing record and workflow.

The project focuses on the record-keeping and operational gaps that follow when a clinic's core work remains on paper.

Goals

Goals for the build

  • Get documentation off paper

    Prescriptions generated by speaking, structured well enough to store and search rather than as a wall of transcribed text.

  • One record that persists

    A medical history that accumulates across visits and belongs to the patient, including records they manage for family members.

  • Bilingual, properly

    English and Urdu across the whole interface, with right-to-left layout and typography that suits Nastaliq rather than Latin text stretched sideways.

  • Operational visibility

    Appointment, staff, and revenue figures for clinic administrators, computed rather than estimated.

  • Appointment coordination

    Patients and clinics need appointments, schedules, and follow-up information to remain connected to the shared record.

  • Safe structured assistance

    Doctors need voice input and structured prescription fields that reject invalid output rather than saving an incomplete prescription.

The goals connect clinical documentation, patient continuity, language access, and safe clinic operations.

Tech stack

Speech

Browser Web Speech API
CostChromium-only, and Urdu recognition quality is limited by the state of the art for the language. A doctor on Safari cannot dictate at all.

Language model

GroqLlama 3.3 70B
CostClinical text is transmitted to a third-party provider. That is disclosed rather than hidden.

Frontend

Next.jsReact
CostRoute protection lives in layout components, so client-side guards are cosmetic. Real enforcement is entirely server-side, which is correct but means the two must never be confused.

Client state

Redux Toolkit Query
CostSlices written at different times diverged in how they read the access token and handle re-authentication.

Backend

Express 5 on Node.js
CostExpress supplies nothing by default, so security headers and rate limiting must be added deliberately. They were not. That is a real gap and it is named again below.

Database

MongoDBMongoose
CostNo multi-document transactions are used, so the revenue ledger and the denormalised counters can diverge if a write sequence fails midway.

Token store

Redis-compatible service over HTTP
CostBecomes a hard dependency of authentication. If it is unreachable, sign-in fails, which happened during testing.

Real-time

Socket.IO
CostRequires a long-lived server process, which conflicts with the serverless deployment target. The consequence is the deployment note below.

Architecture

BrowserPatient portalDoctor portalClinic adminSuper adminWeb Speech APIaudio stays hereServerExpress 5 API96 routesRedis token storeover HTTPMongoDB17 modelsSocket.IOlong-lived onlyExternal servicesGroqllama-3.3-70bGeminihealth assistantCloudinarySMTP relayRESTtranscript
System architecture

BrowserDoctor dictatesen or urManual entryfallbackServerTranscripttext onlyValidateserver-sideProviderGroqJSON modePrescriptionreviewed, savedparsevalidinvalid, nothing saved
Voice prescription: the path, and where it stops

Long-lived hostREST APISocket.IOScheduled jobsPushed liveServerless targetREST APISocket.IOomittedScheduled jobsomittedRead on next load
What each deployment target actually runs

Technologies I work with day to day, from the frontend through the backend to deployment.

Features

Patient

13 features

Manage personal and medical profile

Search clinics and doctors

Filter doctors by specialization

Book, cancel, or reschedule appointments based on doctor availability

View upcoming, past, and completed appointments

Access prescriptions, lab reports, and medical records

Download and share medical records

Maintain medical history across visits

Manage medical records for family members

Chat with doctors in real time

Receive appointment and follow-up reminders

Use AI health assistant when doctors are unavailable

Use the interface in English or Urdu

Doctor

11 features

Manage doctor profile, specialization, and availability

View upcoming appointments and manage the schedule and queue

Accept, reject, cancel, or complete appointments

Manage patient lists with complete medical history

Generate prescriptions using voice input

Convert speech to text using AI

Add diagnosis, notes, and follow-up instructions

Upload and view patient lab reports

View patient medical history with permission-based access

Chat with patients in real time

Track revenue and earnings

Clinic

7 features

Manage clinic profile and settings

Manage doctors and staff within the clinic

Add or remove doctors from the clinic

View and manage doctor schedules and availability

View clinic revenue and performance analytics

Track doctor activity and clinic performance

Manage clinic-level notification preferences

System

10 features

User registration for Super Admin, Clinic Admin, Doctor, and Patient

Secure login and logout

JWT-based authentication with refresh tokens

Role-Based Access Control (RBAC)

Permission-based access to patient medical history

Profile management for personal information, specialization, and images

Account activation and deactivation

Super Admin access across individual clinics

Scheduled analytics snapshots for faster dashboard access

Notification preferences managed per user

API design

AI

Three controllers, and the split is the point. Prescription parsing is deliberately separate from the chat assistant: they use different providers, different prompts, and, critically, different failure behaviour. The assistant may return prose. The parser may not.

  • Parse PrescriptionPOST/api/ai/parse-prescription

    transcript in, JSON out

  • Parse with GroqPOST/api/ai/groq

    structured completion

  • Use Gemini AssistantPOST/api/ai/gemini

    health assistant

Role-scoped resources

Controllers are organised by role first and resource second: doctor/appointment and patient/appointments are different files, not one handler branching on who called it. It duplicates some shape, and it means an authorisation mistake is contained to one role rather than leaking across all of them.

  • PatientsGET/api/doctor/patients
  • Manage PrescriptionPOST/api/doctor/prescription
  • ScheduleGET/api/doctor/schedule
  • RecordsGET/api/patient/records
  • Manage FamilyPOST/api/patient/family
  • View AnalyticsGET/api/clinic/analytics
  • StaffPOST/api/clinic/staff

Auth

Refresh tokens are stored server-side rather than only signed, which is what makes revocation real: signing out invalidates the token everywhere instead of waiting for it to expire. The cost is that the token store is now on the critical path for sign-in.

  • Create AccountPOST/api/auth/register
  • LoginPOST/api/auth/login
  • GooglePOST/api/auth/google

    federated identity

  • Refresh SessionPOST/api/auth/refresh

    rotates, revocable

  • LogoutPOST/api/auth/logout
Sign-incredentialsVerifyMongoDBStore refreshRedis over HTTPIssue tokensaccess + refreshSign-in failsstore unreachable
Authentication, and the edge where it fails

Data model

Seventeen models. The interesting ones are where the record structure meets the identity structure.

Account identifiersUserDoctorPatientAppointmentPrescriptionConnectionRequestClinical profile identifiersMedicalRecordFamilyMemberCliniclookuplookup
Core relationships, and the two identifier spaces

Prescriptionembedded medicine list

The medicine list is variable-length and always read with its parent, so it is embedded. There is no read path that wants a medicine without its prescription.

MedicalRecordheterogeneous, sparsely populated

Vitals, lab results, imaging, and attachments differ from visit to visit. This is the collection that justified a document store over a relational schema: the alternative is a wide table mostly full of nulls, or a migration every time a new result type appears.

Patient and FamilyMemberclinical profile identifiers

A patient account can hold records for people who do not have accounts. Family members are clinical profiles without credentials, which is why a second identifier space exists at all.

RevenueEntry and Analyticsdenormalised, written by a scheduled job

Analytics are snapshots computed on a schedule rather than aggregated per request. It keeps dashboards fast, and it is the reason a failed write sequence can leave the ledger and the counters disagreeing.

Conversation and Messagereferenced, split

Split so a long thread does not grow one document without bound. Message bodies are encrypted with a dedicated key. See the challenge below for what happens when that key is not configured.

ConnectionRequestexplicit relationship

A doctor cannot read a patient's history because they share a clinic. The relationship is a record that has to exist, which makes access auditable rather than implied.

Challenges

The model returned things that were not JSON

Problem

What:

Early prescription parsing came back with explanatory text before the JSON, Markdown code fences around it, or a structure that stopped mid-object.

Why:

Every one of those is a parse failure, and the tempting fix, extracting whatever parsed and saving the rest, is the dangerous one. A prescription form with four of five fields populated looks complete.

How it was solved

Enforced JSON output, a controlled sampling temperature, and server-side validation of the parsed result before anything is written.

Anything invalid falls back to manual entry rather than being saved as an incomplete prescription. The doctor types it instead, which is slower and correct.

The token store went down and took sign-in with it

Problem

What:

During testing the external token store became unreachable. Sign-in failed outright, because refresh tokens could not be written.

Why:

The application handles the store being unconfigured: it falls back. It did not handle the store being configured and then unavailable, which is a different state and the more likely one in production.

How it was solved

The immediate cause was understood and the failure is documented rather than papered over: server-side revocation is worth having, and the price is that the store sits on the authentication critical path.

I have not fixed this. The correct answer is a circuit breaker with a degraded mode that issues shorter-lived tokens when the store is unreachable, and it is not built.

Urdu is not English laid out backwards

Problem

What:

Switching to Urdu produced a layout that was mirrored but wrong: cramped line heights, clipped descenders, and spacing that read as broken to anyone who actually reads Urdu.

Why:

Nastaliq typography has different vertical characteristics from Latin type. A right-to-left flip handles direction and nothing else, and I had assumed direction was the whole problem.

How it was solved

An Urdu-specific typography scale and direction-aware spacing throughout, rather than a single global mirror. Line height, letter spacing, and vertical rhythm are set per script.

Message encryption falls back to the wrong key

Problem

What:

Message bodies are encrypted with a dedicated key. When that key is absent from the environment, the system derives one from the token signing secret instead.

Why:

It means a single leaked secret compromises both authentication and message confidentiality. Two security domains that should fail independently are coupled by a convenience fallback, and the example environment file does not list the dedicated key, so the fallback is the default path, not the exception.

How it was solved

It is documented as a known weakness rather than closed, and I am listing it here for the same reason. The fix is to refuse to start without an explicit key, which is a better failure than starting insecurely.

Practices

  • Patient audio never leaves the browser. the strongest privacy property in the system, and it came from a budget constraint rather than from foresight.
  • Server-side refresh-token revocation. signing out actually invalidates, rather than waiting for expiry.
  • Explicit doctor–patient relationships. access requires a record, so it can be audited.
  • AI output validated before persistence. invalid results are rejected, never partially saved.
  • Scheduled analytics. dashboards read snapshots instead of aggregating live.
  • No credentials in version control. environment files excluded, and the report documenting this system reproduces no values.

Named plainly, because a practices list that only lists successes is marketing: there is no rate limiting and there are no security headers. Express provides neither by default and neither was added. On a system holding clinical data that is the most serious omission on this page.

Metrics

17
Mongoose models

counted

96
Application routes

counted

06
Team size

counted

05
Duration (months)

counted

Lessons

01

Getting the structure right mattered more than getting the transcription perfect. Urdu recognition is imperfect and will stay imperfect, but a doctor correcting two words in a correctly-shaped prescription is doing something very different from a doctor checking whether a form silently dropped a field. Designing the failure case first was the best decision in the project.

02

Choosing a component because it works in the deployment target changes the deployment target. The token store was chosen for serverless reachability, and it became a hard dependency of authentication. Socket.IO was chosen for the right abstraction, and it made the serverless deployment partial. Both were reasonable individually and neither was evaluated against the other.

03

A framework that supplies nothing by default supplies nothing by default. I chose Express knowing it was minimal, then never went back for the headers and the rate limiting that "minimal" was supposed to mean I would add myself. The gap is not an oversight in configuration. It is what happens when the cost of a choice is accepted in principle and never paid.

04

Building something bilingual taught me that internationalisation is a typography problem wearing a translation problem's clothes. The strings were the easy half.

Related

Open to full-time remote roles and freelance work.