FyzioDesk
Software for running a physiotherapy practice, built for Slovakia and Czechia. The calendar and online booking, client records and medical notes, exercise plans with video and adherence, invoices, and a portal where clients book and see their plan.
- Client
- My own product
- My role
- Product, design and all of the code
- Timeline
- August 2026 to now
- Status
- Deployed at app.fyziodesk.sk, mobile apps in progress
- Stack
- NestJS 11 on Fastify, Next.js 16, React 19, Prisma 7, PostgreSQL 18, Redis, BullMQ, MinIO, Expo, Zod


Why I built it
Physiotherapists in Slovakia and Czechia juggle a booking tool, a spreadsheet of clients, paper notes and exercise sheets printed from the internet. Before writing code I compared nine products, from local ones to Cliniko, Jane and Physitrack. None combined native apps for both client and therapist, photo documentation from the treatment table, and exercise adherence as the number that matters.
FyzioDesk is that product. A clinic, its rooms and therapists live in one tenant; each role sees only what it should.

Client record with a pain map
Structured notes from templates, contraindications that stay on top, and a body chart where the therapist marks where it hurts and how much. Medical records can’t be edited once saved: a correction is a new version that points to the old one.

Online booking
Clients pick a service, a time and enter their details. Confirmations go out with an .ics file, reminders 24 and 2 hours before.

Exercise plan
A home programme with video for each exercise. Clients tick exercises off and log pain; adherence is calculated, never stored, so it can’t go stale.

Reports
What was invoiced and what wasn’t, how much time no-shows cost, late cancellations and utilisation per therapist.
Decisions worth mentioning
- Two layers of tenant isolation.Guards check membership in the app, and every table with a
tenant_idhas a PostgreSQL row-level security policy. The API connects as a role without superuser orBYPASSRLS, and refuses to start if that ever changes. - Double booking is impossible, not just unlikely.GiST exclusion constraints over time ranges stop a therapist or a room from being booked twice. A test fires 20 simultaneous bookings for the same slot and expects exactly one to succeed; it found deadlocks between the two constraints, now handled with retries and jitter.
- Medical notes are append-only.A database trigger rejects any change to the content of a saved record. Only the deletion flag may change.
- Č sorts after C.Name columns use an ICU collation for Slovak, so the client list is in the order a Slovak reader expects.
- Photos only with consent, links that expire.Files sit in private MinIO buckets. Uploads and downloads use presigned URLs that last ten minutes and are issued only after a permission check.
- An AI receptionist that can’t see health data.An MCP server gives an AI assistant six tools: find a client, today’s appointments, free slots, book, cancel, unpaid invoices. It uses the public API with an API key and has no route to medical records.
In numbers
- 18 API modules with 271 routes, 72 database models and 75 migrations.
- 110 test files with more than 1,600 test cases, running against real PostgreSQL and MinIO in containers.
- Shared Zod contracts: availability, invoice status, adherence and QR payments are the same code in the API, the web app and the mobile app.
Invoices come with Pay by square and SPAYD QR codes and ISDOC export. Payment gateways, SMS and push are wired and waiting for merchant accounts.
