TL;DR: InterSystems IRIS for Health lets developers perform FHIR CRUD operations directly from backend ObjectScript routines without relying on an HTTP REST layer. Using HS.FHIRServer.Service, developers can create service contexts, build dynamic queries, validate schemas, and retrieve FHIR resources efficiently.
Traditional FHIR implementations typically process requests through an HTTP/REST pipeline. This article explores what happens when that layer is bypassed, and FHIR operations are executed directly within the IRIS backend, focusing on the architecture, service context, query handling, validation, and resource access involved in this approach.
Who This Article Is For: This guide is designed for InterSystems IRIS for Health, HealthShare, and Health Connect developers, and health informatics engineers who need to execute low-overhead FHIR operations directly inside backend ObjectScript routines without relying on external HTTP endpoints.
Core Architecture: The Standard Flow vs. Programmatic Execution
Standard FHIR implementations deployed on InterSystems IRIS for Health, HealthShare, and Health Connect rely on an incoming HTTP pipeline mapped to HS.FHIRServer.Service through the REST application layer. In this standard web architecture, client applications transmit requests over HTTP/HTTPS; the system evaluates security tokens, parses request payloads, and delegates operations down to the storage repository implementation to support Create, Read, Update, and Delete (CRUD) paradigms, along with specialized search capabilities.
By bypassing the standard web server dispatch layer entirely, ObjectScript routines can instantiate backend service contexts and interact directly with the core engine room of the InterSystems IRIS FHIR repository. This approach lets developers build native query objects, manage sort parameters, validate schemas, and run storage-level searches or read records directly with maximum performance and precise control.
GET: Used to read a single resource (GET [base]/Patient/123) or search a collection using query filters (GET [base]/Patient?gender=male&birthdate=ge1980-01-01).POST: Utilized to create a brand-new resource (POST [base]/Patient).PUT: Employed to update an existing resource or upsert it based on an identifier (PUT [base]/Patient/123).DELETE: Used to remove or archive a record (DELETE [base]/Patient/123).
When you throw query parameters into a GET request, you are telling the server how to slice and dice the data. However, how does IRIS process those parameters programmatically when you step away from the browser or Postman? Instead of relying solely on the external HTTP REST layer, you want to execute FHIR operations directly inside your IRIS backend using HS.FHIRServer.Service.
Constructing Query Parameters Programmatically
When performing advanced searches internally, managing query parameters cleanly is essential to prevent malformed queries and ensure optimal performance through the JsonAdvSQL execution engine. InterSystems provides HS.FHIRServer.API.Data.QueryParameters for this exact purpose. Depending on where your data originates, you can construct and populate this object using two primary approaches:
· Approach A: Manual Construction via AddParameter(): If you are dynamically building programmatic search criteria within your application logic (e.g., constructing a query based on internal business rules, user selections in a custom UI, or workflow states), you instantiate the QueryParameters class directly and append filters one by one using the AddParameter() method. This approach gives you absolute control over individual parameter names, modifiers, prefixes, and values before the query ever hits the IRIS storage layer.
· Approach B: Parsing from a Query String via CreateFromQueryString(): When handling raw HTTP requests, you do not need to parse URL parameters manually. InterSystems IRIS provides a native utility method—CreateFromQueryString()—on the query parameter handler class to automate this transformation seamlessly.
Sorting and Linking Data Programmatically
Advanced searches require precise control over result ordering and related clinical records through explicit parameter configuration.
· The Automatic Way: When building standard FHIR applications, developers usually rely on incoming REST URLs. A client might request a list of patients sorted by their last update date in descending order: GET [base]/Patient?gender=female&_sort=-_lastUpdated. When you pass this string into HS.FHIRServer.API.Data.QueryParameters.CreateFromQueryString(), IRIS automatically parses out the _sort parameter.
· The Manual Way: If you are not using a URL query string, or if you want to intercept a request in your custom interactions class and force a specific sort order regardless of what the user asked for, you can construct .tSortKeys manually. Instead of relying on a string parser, you can build the sort array explicitly in ObjectScript.
· Forward Links (_include) : They follow references outward from the matching resource to pull related entities (e.g., searching for MedicationRequest resources and including the referenced Practitioner or Patient [MedicationRequest:patient]).
· Reverse Links (_revinclude): They follow references backward from other resources that point to your match (e.g., searching for a Patient and pulling all Observation or Encounter records that reference that specific patient ID [Observation:patient]).
Technical Reference & Implementation Code Examples
The following ObjectScript code blocks illustrate how to execute these programmatic workflows directly inside InterSystems IRIS:
Manual Parameter Construction:
ClassMethod CreateParamsManually() As HS.FHIRServer.API.Data.QueryParameters
{
Set params = ##class(HS.FHIRServer.API.Data.QueryParameters).%New()
// Equivalent to filtering by ?gender=male
Do params.AddParameter("gender", "male")
// Equivalent to filtering by ?birthdate=ge1980-01-01
Do params.AddParameter("birthdate", "ge1980-01-01")
Quit params
}
Parsing from a Query String:
ClassMethod CreateParamsFromString() As HS.FHIRServer.API.Data.QueryParameters
{
Set queryString = "gender=male&name=Smith"
// Set tQueryString = "patient=123&date=ge2026-01-01&_include=Observation:patient&_count=50"
// Automatically handles keys, values, prefixes (like 'ge'), and modifiers
Set params = ##class(HS.FHIRServer.API.Data.QueryParameters).CreateFromQueryString(queryString)
Return params
}
Schema Metadata Validation via FindSearchParam: To prevent runtime errors caused by invalid query criteria or resource-attribute mismatches, the active schema metadata should be inspected before execution. tStrategy.Schema.FindSearchParam(pResourceType, pParamName) acts as a safety check.
ClassMethod ValidateSearchParameter(pStrategy As HS.FHIRServer.Storage.JsonStrategy, pResourceType As %String, pParamName As %String) As %Boolean
{
// Evaluates if the parameter is valid for the specified resource in the active FHIR version schema
Set searchParam = pStrategy.Schema.FindSearchParam(pResourceType, pParamName)
If $IsObject(searchParam) {
Write "Parameter '", pParamName, "' is valid for resource '", pResourceType, "'.", !
Quit 1
}
Write "Invalid search parameter for this resource schema.", !
Quit 0
}
Direct Search Execution against the Interactions Engine:
ClassMethod ExecuteDirectSearch(pAppKey As %String) As %Status
{
Set tSC = $$$OK
Try {
// Instantiate the FHIR Service, which contains the strategy and interactions objects
Set fhirService = ##class(HS.FHIRServer.Service).EnsureInstance(pAppKey)
// Access the underlying interactions engine through the strategy
Set tInteractions = fhirService.Strategy.GetInteractions()
// Reusing our query parameter builder
Set tParams = ##class(HS.FHIRServer.API.Data.QueryParameters).CreateFromQueryString("gender=male")
// Execute the Search method directly against the Interactions class
// Parameters: (ResourceType, Compartment, CompartmentId, QueryParameters, .SortKeys)
Set tSearchResult = tInteractions.Search("Patient", "", "", tParams, .tSortKeys)
// Iterate through the search results object
Write "Total matches found: ", tSearchResult.Total, !
// Loop through matching resource IDs or entries
While tSearchResult.Next(.tId, .tResourceJson, .tStatusCode) {
Write "Patient ID: ", tId, " | Details: ", tResourceJson.%ToJSON(), !
}
} Catch tException {
Set tSC = tException.AsStatus()
Write "Error during search: ", $System.Status.GetErrorText(tSC), !
}
Quit tSC
}
Direct Resource Retrieval via .Read(): The Read() method on HS.FHIRServer.Storage.JsonAdvSQL.Interactions is the core programmatic function responsible for fetching a specific, individual FHIR resource's JSON data directly from the repository storage layer without running search indexes.
Set pResourceType = "Patient"
Set resourceId = "1"
// Retrieve the resource as a %DynamicObject directly using interactions.Read()
Set tInteractions = fhirService.Strategy.GetInteractions()
Set resourceString = tInteractions.Read(pResourceType, resourceId)
Write "Patient ID: ", resourceId, " | Resource Data retrieved successfully.", !
Common Mistakes to Avoid
· Bypassing Schema Validation: Skipping calls to FindSearchParam() before executing dynamic queries, which can lead to unhandled runtime exceptions when an attribute does not match the active FHIR resource schema version.
· Hardcoding REST Dependency: Forcing internal microservices or batch engines to route through local loopback HTTP requests instead of calling HS.FHIRServer.Service and GetInteractions() directly in ObjectScript.
· Improper Result Traversal: Failing to evaluate result status codes properly or using incorrect property lookups when iterating through HS.FHIRServer.Util.SearchResult object loops.
Frequently Asked Questions (FAQ)
Q: Can you execute FHIR queries without an HTTP server request?
A: Yes. By instantiating HS.FHIRServer.Service and extracting the interactions engine, ObjectScript code can execute searches and read records entirely in-memory.
Q: How do you programmatically add _include parameters?
A: You use the AddParameter() method on your QueryParameters instance, passing _include as the key and the target reference path (e.g., Observation:subject) as the value.
Q: What is the purpose of HS.FHIRServer.API.Data.QueryParameters?
A: It provides a structured mechanism to cleanly build, parse, and encapsulate query criteria, modifiers, and prefixes before execution through the JsonAdvSQL storage layer.
Q: How does .Read() differ from .Search()?
A: Read() takes an explicit Resource Type and logical ID to fetch a single record instantly as a %DynamicObject, whereas Search() evaluates multi-parameter filters across a collection of resources.
Q: How do you handle custom sorting without a URL string?
A: You can manually define sort arrays using .tSortKeys alongside schema definitions retrieved via tStrategy.Schema.FindSearchParam().
Q: Why is schema validation necessary during programmatic execution?
A: It ensures that search keys, attributes, and modifier mappings conform to the active FHIR profile definitions, preventing execution errors.
Practical Recommendations
Always initialize your runtime context using HS.FHIRServer.Service.EnsureInstance(pAppKey) before extracting the interactions engine. Utilize CreateFromQueryString() when processing incoming text maps, and implement schema validation checks via FindSearchParam to guarantee robust, error-free backend integrations.
Conclusion
By bypassing standard REST routing and programmatically invoking the InterSystems IRIS FHIR interactions engine, developers gain direct, low-overhead access to the core repository. Utilizing manual query parameters for searches and the Read() method for targeted resource retrieval provides the maximum speed, precision, and architectural flexibility required for high-performance backend integrations and custom business logic.
Top comments (0)