illustrative rebuild — our own dogfood, not a production migration
A Tourette's symptom-tracking app we built ourselves the normal way: bespoke DynamoDB tables, ~15 CRUD Lambdas, a hand-rolled HIPAA audit log, and data that could never talk to a real EHR. Here's the same app rebuilt on bonfireDB — less code, and FHIR, audit and interop for free.
TicVision is one of our own apps for people living with Tourette's and tic disorders — built by the Simplest Healthcare team, the same team behind bonfireDB. Patients log individual tics, fill in a weekly check-in, and optionally connect a doctor who can view their data. Strip away the domain and it's a familiar shape: auth plus CRUD over a symptom time-series, with a clinician read-side and HIPAA on top.
This is an illustrative rebuild — our own dogfood, grounded in TicVision's actual backend (SAM template, Lambda handlers, docs). Because we build both, the rebuild is us porting our own app, not a customer migration. bonfireDB is early-stage; the "after" reflects bonfire's design and primitives, not a production migration we ran.
TicVision's backend is well-built and conventional — and that's the point. To ship symptom tracking, the team had to design a data model around access patterns, hand-write CRUD, and stand up HIPAA plumbing themselves. None of it is wrong. All of it is work you do instead of the product.
USER#<id>.userId + ticId, plus a UserDateIndex GSI just to read by date.userId + weekStart, with a boolean per weekday.eventId + three GSIs (by user, by resource type, by timestamp) and a 6-year TTL, all hand-built for HIPAA.common/audit_logging.py module every handler imports to write audit rows.APPROVED, then fetch each table.scan() over Tics and WeeklyCheckin, with a hardcoded list of test-user IDs to skip.GET /sync endpoint diffing changes since last_sync_time.And the cost that never shows up in a demo: zero interoperability. The data lives in a proprietary single-team DynamoDB shape. A tic is a row with an intensity number — not an Observation with a coded concept. There is no Bundle to hand to an EHR, no standard a clinician's system can read. The shape is locked in by construction.
TicVision's audit story is an entire subsystem. A 200-line logging module, a dedicated table with three GSIs and a six-year TTL, and a try/except around every audit call in every handler so a logging failure can't take down a read.
# hand-coded access control + manual audit, per handler if request_item["doctorId"] != doctor_id: audit_logger.log_access_event(action="READ", user_id=doctor_id, resource_type="PATIENT_DATA", status="DENIED", ...) return resp(403, "not assigned to you") if request_item["status"] != "APPROVED": return resp(403, "not approved") # then: get_item profile, query tics GSI, query checkin, # and a log_data_access(...) call after every single read
On bonfire, you don't design tables — you write to app-native clinical primitives. The same data becomes standard FHIR R4 resources underneath, and the plumbing TicVision hand-built becomes built-in behavior.
| TicVision on DynamoDB | TicVision on bonfireDB |
|---|---|
| UserProfile table (patient) / Doctor table | Patient / Practitioner (generated underneath) |
| Tics table + UserDateIndex GSI | clinical.observations.record(...) → an Observation (tic type code + severity value + effectiveDateTime) |
| WeeklyCheckin table (boolean per weekday) | clinical.assessments.record(...) → QuestionnaireResponse + scored Observation |
| Doctor portal: hand-coded request / APPROVED / owner checks | Clinical authorization — assigned patients, minimum-necessary, enforced for you |
AuditLog table + 3 GSIs + audit_logging.py + per-call try/except | Deleted. Automatic AuditEvent / provenance on every read and write |
Analytics via full-table scan() + hardcoded test-user filter | App-native aggregate query over a committed projection |
GET /sync diff + offline-first client logic | The reactive query + freshness layer — fresh on commit |
| Cognito UserPool + Client | Keep it — bring your own auth |
Record a tic. Record a check-in. Read the timeline (reactively, fresh on commit). Export FHIR. The access control, the audit trail, and the FHIR shadow are not in this file — they're the platform.
// A tic is an Observation: coded type + severity value + when clinical.observations.record({ patientId, code: "tic-severity", value: intensity, // 1–10 effective: occurredAt, }); // The weekly check-in is a scored assessment clinical.assessments.record("weekly-checkin", { patientId, answers }); // -> QuestionnaireResponse + a scored Observation, kept in sync // The patient timeline — reactive, fresh the instant a tic commits const timeline = useClinicalQuery(api.observations.listByPatient, { patientId }); // Interop on demand — a clean FHIR R4 Bundle const bundle = await clinical.fhir.export(patientId);
No audit_logging.py. No UserDateIndex. No scan() to count tics. No last_sync_time bookkeeping. The doctor read-side becomes clinical authorization — assigned patients, minimum-necessary — instead of an if doctorId != sub by hand. Want the raw resource graph? It's an escape hatch, not your day job.
The DynamoDB version could never do this. Because tics are Observations and check-ins are QuestionnaireResponses — coded concepts, not bespoke columns — TicVision can export to and interoperate with a real EHR. The data is no longer trapped in a single-team shape.
AuditEvent on every read and write — the hand-built AuditLog subsystem is gone.ifs.audit_logging.py.UserDateIndex GSI and the read-by-date glue around it.scan() and its hardcoded exclusions./sync diff endpoint and offline reconciliation bookkeeping.Same auth-plus-CRUD simplicity. Far less code. FHIR, audit and interop for free. This is the kind of app you can build in a weekend — and the reason is that you stopped hand-rolling the clinical data layer.
This case study is an illustrative rebuild of our own app, grounded in TicVision's real backend, not a customer migration we ran in production. bonfireDB is early-stage — we're showing the design and the primitives, told straight.
// Every write returns its freshness lifecycle { status: "committed", views: { notesByPatient: "fresh", timeline: "fresh" }, indexes: { semanticSearch: "pending", agentContext: "pending" } }
TicVision spent its weekends on tables, GSIs, and a HIPAA audit log. Build on app-native primitives instead — and get FHIR, audit and interop for free.
You map each access-pattern-shaped table to an app-native clinical primitive instead of redesigning tables. In this rebuild, a Tics table becomes Observations, a WeeklyCheckin table becomes a QuestionnaireResponse plus scored Observation, and bonfireDB generates the FHIR R4 resources underneath. This is an illustrative rebuild of our own app, not a production migration we ran.
A DynamoDB row stores a tic as an intensity number in a proprietary, single-team shape no EHR can read. A FHIR Observation stores the same tic as a coded concept with a severity value and effectiveDateTime, so it can export to a Bundle and interoperate. bonfireDB lets you write app-native primitives while keeping standard FHIR R4 underneath.
Yes. bonfireDB is the clinical data layer above the FHIR server, not your identity provider, so you bring your own auth and keep your Cognito UserPool. The case study keeps Cognito and only replaces the bespoke tables, CRUD Lambdas, and hand-rolled audit log.
No. The TicVision rebuild deletes its hand-built AuditLog table, three GSIs, six-year TTL, and audit_logging.py module. bonfireDB is designed to write AuditEvent and provenance automatically on every read and write, so you don't wire audit calls into each handler. bonfireDB is early access; this reflects the design, not a certified system.
bonfireDB is the app backend that generates FHIR underneath (not a FHIR server) — the data layer where you write app-native primitives and get FHIR R4, audit, and authorization designed in. It targets developers rebuilding custom DynamoDB or vibe-coded clinical apps who want interop without designing tables. It is open-source and pre-launch, so evaluate it against your needs in early access.