Skip to content
All work

Source available · Reference build · 2026

Enamel

Not one program but four — a practice website, a patient booking area, the clinical admin, and the worker that sends what the practice promised. Forty-six collections, an append-only clinical record, and 468 checks that try to break it.

The day sheet — chairs across the top, time down the side, drag to reschedule
Role
All of it — data model, access policies, seven extensions, the background service, the patient site, provisioning and branding
Year
2026
Languages
EnglishGermanDutchFrench
Stack
DirectusNestJSBullMQTypeScriptReact RouterVue 3PostgreSQLRedisDocker

Most Directus demos model a blog. A blog has one kind of user and nothing worth protecting, so it never has to answer the question that decides whether a real project succeeds. A dental practice answers it on day one: the receptionist books the appointment and takes the payment, so she needs the patient list — and has no business reading the medical alerts. Then it asks harder ones. What happens to the reminder nobody was there to send? Who looked at that record last Tuesday? How long must this be kept, and under whose law?

  1. Four programs, not one

    A public website the practice edits from the same admin it books appointments in — no second CMS to learn. A patient area where someone signs in and books against the real diary, not a request form somebody phones back about. The clinical admin. And a background worker, because the things a practice promises to do later cannot live in a web page that only exists while somebody is looking at it. The website is the part published here; the other three are behind a sign-in, which is the point of them.

  2. Field-level access, and 468 attempts to break it

    Front desk reads contact details and is refused medical alerts, allergies and clinical notes at the field level — and treatment records and the tooth chart outright. The suite signs in as each of the ten roles and asserts both directions: what they must reach, and what they must not. It proves a receptionist cannot read clinical notes by bypassing the admin and asking the database's API directly, that a patient cannot book on somebody else's record by going around the booking screen, and it sabotages the instance on purpose to prove the checks can fail at all.

  3. The scheduling rules live in Postgres

    Opening hours, each clinician's rota, who is on leave, which chairs are out of service — and an exclusion constraint that will not let one dentist be in two places at once. It holds when somebody writes straight to the database, going around every layer of the application, which is the only version of that claim worth making. A violation comes back as a sentence a receptionist can act on rather than an error code, and the appointment's length is worked out from the treatment, so nobody has to remember that a crown fitting needs forty-five minutes.

  4. Queues, because sending fails

    A phone is off, a provider is down, an address is wrong. Four BullMQ queues carry reminders, outbound messages and the timers behind recalls and overdue invoices — and crucially they remember: a job that has failed five times is still sitting there with the reason attached. A practice can see that Mrs Okafor was never told about Tuesday. Without a queue that message would simply have vanished.

  5. A job board behind a one-time door

    You can watch all four queues — what is waiting, what is retrying, what failed and why. It is gated, because the jobs contain patients' names: the admin hands the service the login your browser already has, checks it, and issues a link good for about a minute that works once. There is a Queue operator role that opens the board and nothing else — it cannot read a patient, a note or a radiograph, and a check confirms it.

  6. Logging who read, not just who wrote

    Directus logs who changed what. It does not log who opened a record, and it does not log failed sign-ins — which are the two an information-governance review actually asks about. Both are recorded here, into a log the database itself refuses to let anyone edit or delete. Not a receptionist, not the practice owner, not an administrator.

  7. Teeth are harder than they look

    The chart is built from an append-only findings log, so it shows the mouth now and as it was two years ago, and no entry can be quietly edited later. A chart with 32 boxes for an adult and 20 for a child, picked from the date of birth, is wrong in three directions. Agenesis is ordinary — the lower second premolars are absent in 29.9% of affected people. Supernumerary teeth do not fit FDI at all, which is why ISO 10394 exists: a mesiodens is AB, not 11. And age does not decide dentition, because a retained milk tooth in a forty-year-old keeps its primary designation. So dentition is recorded per tooth, never derived.

  8. Retention as data, with the statute attached

    A dental record is health data — the most sensitive category the GDPR defines. How long each kind must be kept is stored as data with the law it comes from, because the answer is twenty years in the Netherlands, ten in Germany and twenty-five in the UAE and the same software runs in all three. Nothing is deleted automatically; the rules record what the obligation is, and acting on it keeps a human in the loop. Alongside: subject access that assembles the whole record rather than the tables somebody remembered, a disposal register that cannot record a destruction before its authorisation, a breach register written to what Articles 33 and 34 require, and a DPIA that lists the risks which were not mitigated.

The same patient record seen by the front desk and by a dentist — the desk's form ends where the clinical fields begin
The FDI tooth chart, built from an append-only log of findings
The practice's own website, written and edited from the same admin
The practice report — production, case acceptance and recall return as ratios
The BullMQ job board: what is waiting, what is retrying, what failed and why
The admin running in Dutch — collections, fields, notes and bookmarks all translated
The sign-in screen, branded through the Directus settings API