Ownership · 2025 to 2026

An operations console for a healthcare integration engine

Built the API layer, PHI-access auditing features and most of the frontend for a console that operates an HL7 integration engine.

The problem

What was needed, and what made it hard.

Hospitals route clinical messages between systems with integration engines. The people who run one needed a web console to search messages, reprocess and export them, start and stop channels, and watch traffic trends. It is operations tooling, not clinical software. The engine already had its own server API and its own audit log, so the console had to sit in front of both without disturbing either, and it had to stay honest when one channel was slow. My part was the backend-for-frontend, most of the React frontend, the CI/CD pipeline and the staging deployment.

My role: Top contributor: backend-for-frontend, most of the frontend, CI/CD and staging

The system

A global search, from the browser to the engine.

React console 1 Backend for frontend 2 Global search 3 PHI audit 4 Engine API 5 React console 1 Backend for frontend 2 Global search 3 PHI audit 4 Engine API 5
A global search, from the browser to the engine.

Select a component to read what it does and how it fails.

  1. React console. Search, reprocess, export, channel control and dashboards, with focus trapping and keyboard navigation. It shows the gaps in a result instead of hiding them.
  2. Backend for frontend. Proxies the engine's API with CSRF handling, login rate limiting and input validation. When the engine is slow, this layer has to cope.
  3. Global search. Queries channels in batches of 10, with a 10-second timeout each. It fails per channel, and names the channels that timed out.
  4. PHI audit. Searches and message views on patient-ID channels post an audit event to the engine. Fails if the console's own polling adds false entries.
  5. Engine API. The engine's own server API, outside my control. Reprocess, export and channel control go through it. One channel can be slow while others are fine.

Decisions

What I chose, what I turned down, and what it cost.

1Batch the global search and name the gaps

I chose
I made global search query channels in batches of 10, gave each a 10-second timeout, and added a banner naming every channel that timed out.
I turned down
The old code fired one request per channel, all at once, with no timeout and no cap. A comment in it admitted the risk and moved on.
Why
One slow channel could stall the whole search. A result that hides its own gaps is worse than a slow one.
What it cost
A search over many channels now runs in rounds, so the best case is slower than firing everything at once.

2Keep the console out of the audit log

I chose
I moved the dashboard's polling to a coarser endpoint and filtered in the browser, then built the PHI-access auditing feature on top.
I turned down
Building auditing on a log that the console's own background polling was already filling with false entries.
Why
An audit log full of noise is no use to the person who has to read it.
What it cost
The dashboard fetches more than it shows and does the filtering itself.

3Replace the hidden time picker with a modal

I chose
I replaced the hidden native datetime input with a custom jump-to-time modal.
I turned down
Hiding the native input behind an icon, which was the original design.
Why
Firefox draws the time part inline inside the input, so hiding the input hid the time picker, in Firefox only.
What it cost
A custom control that I have to keep accessible and consistent myself.

What broke

What I noticed, what caused it, and how I fixed it.

Every trend count was too high

What I saw
Each message count on the dashboard trend chart was inflated.
The cause
Every data point summed connector-level rows on top of the channel-level aggregate that already included them.
The fix
I kept only the channel-level aggregate rows.

Formatting changed HL7 messages

What I saw
Viewing a pipe-delimited HL7 message with formatting on added stray brackets.
The cause
Every message went through the XML formatter, and HL7 is not XML.
The fix
I added XML detection, so anything else is shown untouched.

The staging image would not build

What I saw
Staging builds failed outright.
The cause
One copy step staged a single script, not the directory its dependencies lived in.
The fix
I copied the whole directory.

Outcome

What came of it, and what it was built with.

  • I shipped the feature build and set up the CI/CD pipeline and the Traefik staging deployment. The client then paused feature work, and my later commits are deployment and infrastructure migration only.
  • The console has ARIA labels, focus trapping and keyboard navigation.
  • I have no figures for users, channels or messages handled, so I make no usage claim.

Built with

  • React 19
  • TypeScript
  • Vite
  • Node.js
  • Express-style BFF
  • Sequelize
  • Jest
  • Docker
  • Traefik
  • CSRF protection
  • Monorepo

The rule I took from this

The line I keep from this project.

A partial result should look partial.

Rule 5 of 10

Keep reading