Architecture
Baton
A solution-architecture summary — how the system is designed, and why. Verified from the source code.
- Type of system
- Multi-tenant clinical platform (patient portal, clinician console, public booking)
- Users
- Patients, clinicians and clinic administrators
- Hosting
- United Kingdom (self-hosted, Docker)
- Main stack
- FHIR backbone (Medplum), React, Next.js, PostgreSQL
- Status
- Live — in daily clinical use
- Built for
- Moanv's own product (first clinic live: Plexus Psychiatry)
The problem
A private mental-health clinic's work is a relay: booking, payment, ID check, questionnaires, the consultation, the report, prescribing, the GP letter, discharge. Stitched together from a booking tool, a spreadsheet and a separate records system, things get retyped and dropped between them — and it all has to hold up in a regulated setting handling sensitive health data and controlled-drug prescribing.
How it fits together
Users
Patients · clinicians · administrators
Front end
Patient app (Next.js) + staff console (React)
Tenant boundary
Backend / API
FHIR server (Medplum) + sandboxed server “bots”
Database
PostgreSQL 16 + Redis
Each clinic is a separate project with its own records and access policies, sharing one common library.
External services
- Anthropic Claude — report drafting
- Stripe — payments
- Microsoft Teams (Graph) — video
- Mailjet — email
- NHS dm+d / ODS — medicines & GP lookup
Tech stack
Front end
- Staff console: React 19, Vite, Mantine
- Patient app: Next.js 15, React 19, Tailwind
Backend
- Medplum (FHIR R4) on Node
- Server logic as sandboxed Medplum bots
- Patient-app server routes
Database
- PostgreSQL 16
- Redis 7 (both on Docker volumes)
Hosting
- United Kingdom
- Single Linux host, Docker Compose, nginx, Let's Encrypt TLS
- Deployed via Azure DevOps over SSH
Third-party
- Anthropic Claude (reports)
- Stripe (payments)
- Microsoft Teams via Graph (video)
- Mailjet (email)
- NHS Terminology Server & ODS (no patient data sent)
Key design decisions
Build on a FHIR backbone (Medplum), not bespoke CRUD
Why: Clinical data has a standard model; using it means records interoperate and access rules attach to the data itself, not just the screens.
Trade-off: A heavier backend and a real learning curve versus a simple custom database.
Multi-tenant from day one — a separate project per clinic
Why: Baton is a platform, not a one-off. A new clinic onboards onto the same system, and each clinic's records stay isolated.
Trade-off: More careful access-policy design up front than a single-clinic build would need.
AI drafts, humans decide
Why: Assessment reports are drafted only from a fixed evidence pack for one appointment, and a clinician signs. The AI never makes a clinical decision — deliberately staying outside medical-device territory.
Trade-off: The AI can't ‘finish’ a report; a clinician always reviews and signs.
No self-registration
Why: Nobody should create their own clinical account. Staff are invited; patients are created through booking; machines use OAuth client credentials.
Trade-off: An extra onboarding step, by design.
Own the whole pathway rather than buy point tools
Why: One record from booking to discharge means nothing is retyped between a booking tool and a records system.
Trade-off: We own the entire surface and its maintenance.
Security & data protection
- Hosted in the United Kingdom.
- TLS everywhere (Let's Encrypt, auto-renewing) with HSTS.
- Access by role enforced on the server: a consultant sees only their own patients; an administrator sees no clinical notes.
- Two-factor authentication required for staff.
- Full audit trail — every read, search and write is recorded against the user; staff can create audit entries but never read or edit them.
- No self-registration; staff are invited, machine access uses OAuth client credentials.
- Payments are confirmed by a signed Stripe webhook on the server, never by the browser.
- Server-side validation of every booking, including an NHS-number checksum and an 18+ rule.
- Per-clinic AI keys; the model is given only one appointment's evidence pack and cannot reach anything else.
Built to hold up
- Performance
- A server-rendered patient app and a console that loads one patient chart at a time; media served directly by nginx.
- Availability
- A single UK host with a post-deploy health check; deploys are scripted and repeatable.
- Scalability
- Multi-tenant by project — a new clinic is added without standing up new infrastructure for it.
What we'd do next
- Add a Content-Security-Policy across the clinical surfaces to complement the existing security headers.
- Extend the automated access-control tests to cover every staff role, not only the patient boundary.
- Formalise documented retention and deletion schedules for records, transcripts and audit logs.