DL
Back to Blog
TechFebruary 2, 2026·5 min read

FHIR Resources: Patient, Observation, and Beyond

An overview of the FHIR R4 resources FhirHub uses -- Patient, Observation, Condition, MedicationRequest, and more -- with LOINC codes, typed DTOs, and the API translation layer.

D

David Le

FHIR Resources: Patient, Observation, and Beyond

By David Le -- Part 5 of the FhirHub Series

FHIR (Fast Healthcare Interoperability Resources) defines a standard way to represent clinical data. FhirHub implements several FHIR R4 resource types, each mapped to typed DTOs and exposed through REST endpoints. This post covers the resources we use, the LOINC codes that identify clinical measurements, and how the API translates between FHIR and the frontend.

FHIR R4 Resources in FhirHub

FhirHub works with these FHIR resource types:

ResourcePurposeExample
PatientDemographics and identifiersName, DOB, gender, contact info
ObservationVital signs and lab resultsBlood pressure, glucose level
ConditionDiagnoses and health problemsHypertension, Type 2 Diabetes
MedicationRequestMedication ordersLisinopril 10mg daily
DiagnosticReportLab report panelsBasic Metabolic Panel
EncounterPatient visitsOffice visit, ER admission
ProcedureClinical proceduresBlood draw, ECG
ImmunizationVaccination recordsCOVID-19 vaccine
AllergyIntoleranceAllergiesPenicillin allergy
DocumentReferenceClinical documentsDischarge summary PDF

The API Layer

The PatientsController exposes RESTful endpoints for clinical data:

[ApiController]
[Route("api/patients")]
[Authorize]
public class PatientsController : ControllerBase
{
    [HttpGet]             // GET /api/patients
    [HttpGet("{id}")]     // GET /api/patients/{id}
    [HttpPost]            // POST /api/patients
    [HttpGet("{id}/vitals")]       // GET /api/patients/{id}/vitals
    [HttpGet("{id}/vitals/chart")] // GET /api/patients/{id}/vitals/chart
    [HttpPost("{id}/vitals")]      // POST /api/patients/{id}/vitals
    [HttpGet("{id}/conditions")]   // GET /api/patients/{id}/conditions
    [HttpPost("{id}/conditions")]  // POST /api/patients/{id}/conditions
    [HttpGet("{id}/medications")]  // GET /api/patients/{id}/medications
    [HttpPost("{id}/medications")] // POST /api/patients/{id}/medications
    [HttpGet("{id}/labs")]         // GET /api/patients/{id}/labs
    [HttpPost("{id}/labs/orders")] // POST /api/patients/{id}/labs/orders
    [HttpGet("{id}/timeline")]     // GET /api/patients/{id}/timeline
}

Every endpoint is protected with [Authorize] and applies specific policies. Write endpoints also have rate limiting:

[HttpPost("{id}/vitals")]
[Authorize(Policy = AuthorizationPolicies.CanWriteVitals)]
[EnableRateLimiting("WriteOperations")]
public async Task<IActionResult> RecordVitals(
    string id,
    [FromBody] RecordVitalsRequest request,
    CancellationToken ct)
{
    var result = await _patientService.RecordVitalsAsync(id, request, ct);
    return Created($"/api/patients/{id}/vitals", result);
}

LOINC Codes

LOINC (Logical Observation Identifiers Names and Codes) is the standard coding system for clinical observations. FhirHub maps vitals to specific LOINC codes:

// frontend/src/lib/clinical-rules.ts
export const LOINC_CODES = {
  SYSTOLIC_BP: "8480-6",
  DIASTOLIC_BP: "8462-4",
  BLOOD_PRESSURE: "85354-9",    // Blood pressure panel
  HEART_RATE: "8867-4",
  BODY_TEMPERATURE: "8310-5",
  BODY_WEIGHT: "29463-7",
  RESPIRATORY_RATE: "9279-1",
  OXYGEN_SATURATION: "2708-6",
} as const;

Each vital sign in the system is identified by its LOINC code rather than a display name. This ensures interoperability -- any FHIR-compliant system will recognize 8480-6 as systolic blood pressure.

FHIR to DTO Mapping

Raw FHIR resources are verbose and deeply nested. The API maps them to flattened DTOs before sending to the frontend. For example, a FHIR Patient resource with extensions, identifiers, and contact points gets mapped to a clean DTO with just the fields the UI needs.

The mapping happens in the repository layer using the Hl7.Fhir.R4 library:

HAPI FHIR → Hl7.Fhir.R4 objects → DTOs → JSON response

This separation means:

  • The frontend never deals with FHIR complexity
  • DTO shapes can evolve independently of the FHIR standard
  • Validation happens at both the FHIR level (via HAPI) and the API level (via FluentValidation)

Additional Controllers

Beyond patient data, FhirHub has controllers for:

  • DashboardController -- Aggregate metrics and system overview
  • ClinicalController -- Cross-patient clinical data
  • ExportsController -- Bulk data export management
  • UsersController -- Keycloak user administration
  • AuditLogController -- Activity audit trail

Each follows the same pattern: thin controller, service layer for business logic, repository for HAPI FHIR communication.

Creating FHIR CodeableConcepts

When recording new clinical data, the frontend constructs proper FHIR CodeableConcepts:

// frontend/src/lib/clinical-rules.ts
export function createVitalCode(
  loincCode: string,
  displayName: string
): CodeableConceptDTO {
  return {
    coding: [
      {
        system: "http://loinc.org",
        code: loincCode,
        display: displayName,
      },
    ],
    text: displayName,
  };
}

This ensures every observation created through FhirHub includes proper LOINC coding, making the data portable across FHIR-compliant systems.

What's Next

In Part 6, we'll look at how vitals data gets visualized with interactive Recharts line charts, including reference range overlays and multi-vital selection.


Find the source code on GitHub Connect on LinkedIn

Related Projects

Featured

FhirHub

A healthcare data management platform built on the HL7 FHIR R4 standard, providing a comprehensive web interface for managing patient clinical data including vitals, conditions, medications, lab orders, and bulk data exports with role-based access control and full audit logging.

Next.js 16
React 19
Typescript
Tailwind CSS 4
+8