# Building a FHIR Adapter from Scratch: Why Rust, Why Now

# The Problem: Healthcare Data in Silos

Healthcare systems are a mess. I don't mean that lightly—after years of working with legacy hospital databases, I've seen it all: patient data stored in decades-old MySQL schemas (or even Microsoft Access), custom field names that made sense to someone in 1998, and zero interoperability between systems.

When a doctor needs to access a patient's complete medical history, they often can't. The data exists, scattered across multiple incompatible systems, each speaking its own dialect.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767622141000/9ef3db8d-c3ef-4c72-a086-47ec29518967.png align="center")

FHIR (Fast Healthcare Interoperability Resources) was supposed to solve this. It's a modern standard for exchanging healthcare information electronically. But there's a catch: most healthcare providers run on legacy systems that predate FHIR by decades.

## The Solution: A Bridge Between Worlds

I decided to build a **FHIR adapter**—a system that sits between legacy databases and modern FHIR-based applications. It translates old-school database schemas into standard FHIR resources on the fly.

But here's where it gets interesting: this isn't just for one hospital. It's **multi-tenant**, meaning hundreds of healthcare organizations can use the same adapter instance, each with their own completely isolated data and custom database schemas.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767622167322/0ff0eb3d-42a3-4ffa-9d16-cc0554401101.png align="center")

## Why Rust?

I'll be honest: I started from zero with Rust. No production experience, just curiosity and a belief that Rust was the right tool for this job.

Here's why I chose it:

### 1\. **Performance Matters in Healthcare**

When a doctor requests a patient's records, they need them *now*. Not in 5 seconds, not after a loading spinner—now. Rust's zero-cost abstractions and performance characteristics meant I could build a system that handles thousands of requests per second without breaking a sweat.

### 2\. **Memory Safety Without Garbage Collection**

Healthcare data is sensitive. A memory leak or buffer overflow isn't just a bug—it's a potential HIPAA violation. Rust's borrow checker guarantees memory safety at compile time, eliminating entire classes of vulnerabilities that plague C/C++ systems.

### 3\. **Fearless Concurrency**

A multi-tenant system means handling hundreds of database connections simultaneously, each to different legacy databases. Rust's ownership model makes concurrent programming not just possible, but safe and predictable.

```rust
// This is legal Rust - the compiler guarantees safety
async fn handle_request(tenant_id: &str) -> Result<Patient> {
    let pool = tenant_pool_manager.get_pool(tenant_id).await?;
    let config = config_resolver.get_config(tenant_id).await?;

    // Multiple async operations, all safe
    let patient = fetch_patient(&pool, &config).await?;
    Ok(patient)
}
```

### 4\. **Learning Curve as a Feature**

Yes, Rust is hard. The borrow checker will fight you. You'll spend hours debugging lifetime errors. But here's the thing: **every error you fix at compile time is a bug that won't crash in production**.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767622211398/f11d9a7b-66a0-4d04-80a2-812bd89792a6.png align="center")

## The Tech Stack

After evaluating options, here's what I landed on:

* **Backend:** Rust + Axum (web framework)
    
* **Database:** MySQL (for adapter config) + dynamic connections to tenant databases
    
* **Authentication:** Keycloak (enterprise SSO)
    
* **Admin Panel:** Next.js 14 + TypeScript
    
* **Deployment:** Docker + systemd
    

## Early Decisions That Mattered

### Decision 1: Dynamic Configuration Over Code Generation

Instead of generating code for each tenant's schema, I built a **dynamic mapping engine** that reads configuration at runtime. This means:

* ✅ No recompilation needed when onboarding a new tenant
    
* ✅ Tenant admins can configure mappings via UI
    
* ❌ More complex runtime logic
    

### Decision 2: Compile-Time Safety Where Possible, Runtime Flexibility Where Needed

I used Rust's type system aggressively for the core adapter logic, but accepted `serde_json::Value` for the dynamic mapping layer. The tradeoff was worth it.

### Decision 3: Admin Panel in TypeScript, Not Rust

I could have built the admin UI in Rust (Yew, Leptos, etc.), but Next.js was the pragmatic choice. Development velocity matters, and React's ecosystem is unmatched for building complex forms.

## What's Coming Next

In the next posts, I'll dive deeper into:

* **Part 2:** Multi-tenant architecture and database isolation
    
* **Part 3:** The dynamic mapping engine that powers it all
    
* **Part 4:** Security, authentication, and audit logging
    
* **Part 5:** Building an admin panel that doesn't suck
    

## Starting from Zero

When I started this project, I had:

* Zero production Rust experience
    
* A vague understanding of FHIR
    
* A belief that healthcare IT could be better
    

Six months later, I have:

* A working multi-tenant FHIR adapter
    
* 20,000+ lines of Rust
    
* Battle scars from fighting the borrow checker (and winning)
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1767622246117/968558eb-bb1c-4a50-b5f9-9167502070d0.png align="center")

If you're considering Rust for a production project, here's my advice: do it. The learning curve is steep, but the payoff is real. You'll write better code, catch bugs earlier, and sleep better knowing your production system won't randomly segfault at 3 AM.

---

*This is Part 1 of a 5-part series documenting my journey building a multi-tenant FHIR adapter from scratch. Follow along at* [*compile.care*](https://compile-care.ghost.io/) *for updates.*
