Key Takeaways:
FHIR R4 is the ONC-mandated standard for healthcare interoperability. This tutorial walks developers through the fundamentals — resources, REST API operations, SMART on FHIR authorization, and code examples — with enough depth to build a working integration.
FHIR uses RESTful APIs and JSON/XML, making it accessible to any developer with web API experience. The learning curve is the healthcare domain knowledge, not the technology.
This tutorial covers setting up a development environment, working with core FHIR resources (Patient, Observation, Condition, MedicationRequest), FHIR REST operations, SMART on FHIR authorization, and testing/validation.
Code examples are provided in Python and JavaScript — the two most common languages for healthcare API development.
FHIR Fundamentals
FHIR (Fast Healthcare Interoperability Resources, pronounced “fire”) is an HL7 standard for exchanging healthcare data using modern web technologies. If you have built REST APIs, you already understand 80% of how FHIR works. The remaining 20% is healthcare-specific data models, terminology, and authorization patterns.
What makes FHIR different from general REST APIs: Resources have predefined structures defined by the FHIR specification — you cannot invent your own schema. Terminology bindings tie data elements to standard code systems (SNOMED CT, LOINC, RxNorm, ICD-10). Authorization uses SMART on FHIR (OAuth 2.0 with healthcare-specific scopes). Search operations support healthcare-specific query patterns (chained searches, includes, reverse includes). Conformance is enforced through Implementation Guides (IGs) that constrain base resources for specific use cases.
For the broader integration context (HL7v2, FHIR, Mirth Connect, and architecture patterns), see our healthcare integration guide.
Setting Up Your Development Environment
FHIR Test Servers
You do not need access to a production EHR to learn FHIR. Public test servers provide free sandboxes with synthetic data.
HAPI FHIR Test Server — http://hapi.fhir.org/baseR4 — Open-source FHIR server with full R4 support. No authentication required for the public sandbox. Best for learning and experimentation.
SMART Health IT Sandbox — https://launch.smarthealthit.org — Provides a SMART on FHIR launch environment for testing EHR-launched apps. Includes synthetic patient data and a simulated EHR interface.
EHR Vendor Sandboxes — Epic (Open Epic sandbox), Oracle Health (Developer sandbox), athenahealth (Developer portal). Require developer program registration. Use these when building production integrations with specific EHR platforms.
Tools
Postman or Insomnia — For exploring FHIR APIs interactively. Import the FHIR R4 resource definitions for autocomplete support.
FHIR Validator — https://validator.fhir.org — Validates FHIR resources against the specification and implementation guides.
Touchstone — https://touchstone.aegis.net — FHIR conformance testing platform for validating your implementation against specific IGs.
FHIR Resource Structure
Every FHIR resource follows a consistent structure.
{
"resourceType": "Patient",
"id": "example-patient-123",
"meta": {
"versionId": "1",
"lastUpdated": "2026-03-15T10:30:00Z"
},
"identifier": [
{
"system": "http://hospital.example.org/mrn",
"value": "MRN-12345"
}
],
"name": [
{
"family": "Smith",
"given": ["John", "Michael"]
}
],
"gender": "male",
"birthDate": "1985-07-15"
}Key structural elements:
resourceType — Identifies which FHIR resource this is (Patient, Observation, Condition, etc.). Always required.
id — Server-assigned unique identifier for this resource instance.
meta — Metadata including version, last update timestamp, and profile declarations.
identifier — Business identifiers (MRN, SSN, insurance ID). A resource can have multiple identifiers from different systems.
Data types — FHIR uses defined data types: string, integer, boolean, dateTime, code (bound to a value set), CodeableConcept (code + display text from a terminology), Reference (pointer to another resource), Quantity (number + unit), and Period (start/end datetime).
Working with Core FHIR Resources
Patient
The Patient resource represents demographics and administrative information about a person receiving care.
Key elements: identifier (MRN, insurance ID), name, gender, birthDate, address, telecom (phone, email), contact (emergency contacts), communication (preferred language), and generalPractitioner (reference to the PCP).
Observation
Observation is the workhorse resource for clinical data — vital signs, lab results, clinical assessments, social history, and device measurements.
{
"resourceType": "Observation",
"status": "final",
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "vital-signs"
}
]
}
],
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8480-6",
"display": "Systolic blood pressure"
}
]
},
"subject": {
"reference": "Patient/example-patient-123"
},
"effectiveDateTime": "2026-03-15T10:30:00Z",
"valueQuantity": {
"value": 128,
"unit": "mmHg",
"system": "http://unitsofmeasure.org",
"code": "mm"
}
}Critical detail: The code element uses LOINC codes for observation types. Using the correct LOINC code is essential — a blood pressure reading coded incorrectly will not be recognized by receiving systems.
Condition
Condition represents diagnoses, problems, and health concerns.
Key elements: clinicalStatus (active, recurrence, relapse, inactive, remission, resolved), verificationStatus (unconfirmed, provisional, differential, confirmed), category (problem-list-item, encounter-diagnosis), code (ICD-10 or SNOMED CT), subject (reference to Patient), onsetDateTime, and abatementDateTime.
MedicationRequest
MedicationRequest represents a prescription or medication order.
Key elements: status (active, completed, cancelled), intent (order, plan, proposal), medicationCodeableConcept (RxNorm code for the medication), subject (Patient reference), dosageInstruction (dose, frequency, route), dispenseRequest (quantity, refills), and requester (prescribing provider reference).
DiagnosticReport
DiagnosticReport represents a complete lab report or imaging study — wrapping multiple Observations (individual test results) into a single report with context.
Encounter
Encounter represents a patient visit — inpatient admission, outpatient visit, ED visit, or telehealth session. Links together all clinical data generated during that visit.
FHIR REST API Operations
FHIR uses standard HTTP methods.
Read a resource:
GET /Patient/example-patient-123Returns the Patient resource with ID example-patient-123.
Search for resources:
GET /Patient?family=Smith&birthdate=1985-07-15Returns all Patient resources matching the search criteria.
Create a resource:
POST /Observation
Content-Type: application/fhir+json
{body: Observation resource JSON}Server assigns an ID and returns the created resource with a 201 Created status.
Update a resource:
PUT /Patient/example-patient-123
Content-Type: application/fhir+json
{body: updated Patient resource JSON}Replaces the existing resource. Returns 200 OK.
Delete a resource:
DELETE /Observation/obs-456Removes the resource. Returns 204 No Content.
Batch/Transaction:
POST
Content-Type: application/fhir+json
{body: Bundle resource with multiple operations}Executes multiple operations in a single request. Transactions are atomic (all succeed or all fail). Batches are independent (each operation succeeds or fails independently).
Search Parameters and Queries
FHIR search is powerful and healthcare-specific.
Basic search:
GET /Observation?patient=Patient/123&category=vital-signsDate range:
GET /Observation?patient=Patient/123&date=ge2026-01-01&date=le2026-03-31ge = greater than or equal. le = less than or equal.
Include related resources:
GET /MedicationRequest?patient=Patient/123&_include=MedicationRequest:requesterReturns MedicationRequests AND the referenced Practitioner resources (prescribers) in a single response — avoiding N+1 query problems.
Reverse include:
GET /Patient/123&_revinclude=Condition:subjectReturns the Patient AND all Conditions that reference that Patient.
Chained search:
GET /Observation?patient.name=Smith&code=8480-6Search Observations where the referenced Patient’s name is Smith and the observation code is systolic BP.
Paging: FHIR search results return as Bundles with pagination links (next, previous). Default page size varies by server — typically 10–50 resources per page.
SMART on FHIR Authorization
SMART on FHIR adds healthcare-specific authorization to standard OAuth 2.0. It is the ONC-mandated standard for third-party app authorization with EHR systems.
EHR Launch Flow
The app launches from within the EHR (the user is already logged in, the patient is already selected).
Step 1: EHR calls your app’s launch URL with iss (FHIR server URL) and launch (launch context token).
Step 2: Your app discovers the authorization endpoints from /.well-known/smart-configuration or /metadata.
Step 3: Your app redirects to the authorization endpoint with requested scopes.
Step 4: The authorization server returns an authorization code.
Step 5: Your app exchanges the code for an access token (and optionally a refresh token).
Step 6: Your app uses the access token to call FHIR APIs.
Scopes
SMART scopes define what data the app can access.
patient/Patient.read — Read the current patient’s demographics. patient/Observation.read — Read the current patient’s observations. patient/MedicationRequest.read — Read the current patient’s medications. launch/patient — Receive the patient context from the EHR launch. openid fhirUser — Get the authenticated user’s identity.
Standalone Launch Flow
The app launches independently (not from within an EHR). The user authenticates directly with the FHIR server and selects a patient.
This flow is used for patient-facing apps where the patient logs in directly — patient portals, personal health apps, and care management tools.
Code Examples: Python
Read a Patient
import requests
base_url = "http://hapi.fhir.org/baseR4"
patient_id = "example-patient-123"
response = requests.get(
f"{base_url}/Patient/{patient_id},
headers={"Accept": "application/fhir+json"}
)
patient = response.json()
print(f"Name: {patient['name'][0]['given'][0]} {patient['name'][0]['family']})
print(f"DOB: {patient['birthDate']})
print(f"Gender: {patient['gender']})Search for Observations
params = {
"patient": f"Patient/{patient_id},
"category": "vital-signs",
"date": "ge2026-01-01",
"_sort": "-date",
"_count": 10
}
response = requests.get(
f"{base_url}/Observation",
params=params,
headers={"Accept": "application/fhir+json"}
)
bundle = response.json()
for entry in bundle.get("entry", []):
obs = entry["resource"]
code = obs["code"]["coding"][0]["display"]
value = obs.get("valueQuantity", {}).get("value", "N/A")
unit = obs.get("valueQuantity", {}).get("unit", )
date = obs.get("effectiveDateTime", "Unknown")
print(f"{date}: {code} = {value} {unit})Create an Observation
observation = {
"resourceType": "Observation",
"status": "final",
"category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "vital-signs"}]}],
"code": {"coding": [{"system": "http://loinc.org", "code": "8480-6", "display": "Systolic blood pressure"}]},
"subject": {"reference": f"Patient/{patient_id}},
"effectiveDateTime": "2026-03-15T14:30:00Z",
"valueQuantity": {"value": 132, "unit": "mmHg", "system": "http://unitsofmeasure.org", "code": "mm"}
}
response = requests.post(
f"{base_url}/Observation",
json=observation,
headers={"Content-Type": "application/fhir+json", "Accept": "application/fhir+json"}
)
created = response.json()
print(f"Created Observation ID: {created['id']})Code Examples: JavaScript
Read a Patient (Node.js)
const response = await fetch(
`${baseUrl}/Patient/${patientId}`,
{ headers: { "Accept": "application/fhir+json" } }
);
const patient = await response.json();
console.log(`Name: ${patient.name[0].given[0]} ${patient.name[0].family}`);Search with _include
const params = new URLSearchParams({
patient: `Patient/${patientId}`,
_include: "MedicationRequest:requester",
status: "active"
});
const response = await fetch(
`${baseUrl}/MedicationRequest?${params}`,
{ headers: { "Accept": "application/fhir+json" } }
);
const bundle = await response.json();
bundle.entry.forEach(entry => {
if (entry.resource.resourceType === "MedicationRequest") {
const med = entry.resource.medicationCodeableConcept?.coding?.[0]?.display;
console.log(`Medication: ${med}`);
}
});Testing and Validation
Resource Validation
Validate your FHIR resources against the specification and relevant implementation guides before sending them to production servers.
FHIR Validator — https://validator.fhir.org — Paste JSON/XML and validate against R4 base spec or specific IGs (US Core, USCDI).
HAPI FHIR Validator — Available as a Java library for automated validation in CI/CD pipelines.
Conformance Testing
Touchstone — Automated conformance testing against specific IGs. Useful for verifying that your FHIR server or client implementation meets US Core, USCDI, or other required profiles.
EHR Sandbox Testing
Before integrating with a production EHR, test extensively against the vendor’s sandbox. Epic’s sandbox, Oracle Health’s test environment, and athenahealth’s developer portal all provide synthetic patient data and simulated EHR behavior.
The sandbox is not identical to production — vendor-specific quirks, custom extensions, and undocumented behaviors may surface only in production. Plan for a testing phase against the production environment (with test patients) before go-live.
Common Integration Pitfalls
Assuming FHIR implementations are identical across EHRs. Epic’s FHIR implementation, Oracle Health’s FHIR implementation, and athenahealth’s FHIR implementation all conform to FHIR R4 but differ in resource coverage, search parameter support, custom extensions, and error handling. Test against each specific EHR you need to support.
Ignoring pagination. FHIR search results are paginated. If you only read the first page, you miss data. Always follow the next link in the Bundle until there are no more pages.
Hardcoding terminology codes. LOINC codes, SNOMED codes, and RxNorm codes should come from configuration or terminology services — not hardcoded strings scattered through your codebase. Codes change and expand over time.
Not handling missing data. FHIR resources frequently have optional elements that may not be populated. Robust code must handle missing elements gracefully — not crash on a null reference.
Skipping SMART on FHIR. Building custom authentication instead of using SMART on FHIR means your app cannot launch from within EHRs and will not meet ONC certification requirements. Use the standard.
Not planning for rate limits. EHR FHIR APIs impose rate limits. Bulk operations and aggressive polling will get throttled. Use Bulk Data Access for large dataset retrieval and implement exponential backoff for rate limit responses.
Need Help with FHIR Integration? Building a FHIR integration with Epic, Oracle Health, or athenahealth? Schedule a free consultation with our integration architects. Talk to Our Integration Team →
Related Resources:
- Healthcare Integration Guide: HL7, FHIR & Mirth Connect
- FHIR API Development Services
- Mirth Connect Integration Services
- Epic EHR Integration Guide (Blog)
- Healthcare Interoperability Explained (Blog)
- Healthcare API Security Best Practices (Blog)
- EHR/EMR Development & Integration
- EHR Integration Cost Guide
- FHIR Readiness Assessment
- HIPAA Compliance Guide
- Case Study: Mirth Connect Migration
- Free Consultation
Frequently Asked Questions
No. FHIR is independent of HL7v2 and uses modern web technologies (REST, JSON) that any web developer can work with. HL7v2 knowledge helps when integrating with legacy systems that do not yet support FHIR, but it is not a prerequisite for FHIR development.
FHIR R4 (Release 4). It is the ONC-mandated version, the most widely implemented by EHR vendors, and the version required for US healthcare interoperability compliance. R5 exists but is not yet widely adopted. R6 is in ballot.
Technically yes — FHIR APIs can use any authentication method. But for EHR integration, SMART on FHIR is required by ONC certification. If your app needs to work with certified EHRs, you need SMART on FHIR.
All FHIR API calls transmitting PHI must use TLS 1.2+. Access tokens must be securely stored and transmitted. Audit logging must capture every FHIR API call (who, what resource, when). Access scopes must enforce minimum necessary access. See our HIPAA compliance guide.
Taction provides FHIR API development services including FHIR server implementation, SMART on FHIR app development, and EHR-specific integration. Schedule a free consultation to discuss your integration needs.
Ready to Discuss Your Project With Us?
Your email address will not be published. Required fields are marked *
What's Next?
Our expert reaches out shortly after receiving your request and analyzing your requirements.
If needed, we sign an NDA to protect your privacy.
We request additional information to better understand and analyze your project.
We schedule a call to discuss your project, goals. and priorities, and provide preliminary feedback.
If you're satisfied, we finalize the agreement and start your project.

![Mobile App Development: The Complete Guide [2026]](https://admin.tactionsoft.com/wp-content/uploads/2026/04/Healthcare-Cloud-Computing.png)