FHIR® is the registered trademark of HL7® and is used with the permission of HL7®. Use of the FHIR trademark does not constitute endorsement of the contents of this repository by HL7®.
This project provides a set of packages for working with the HL7® FHIR® standard in Go. You only need to implement some interfaces and get a REST implementation out-of-the box.
This includes model types and interfaces modeling capabilities that you can use to build custom FHIR® servers. A REST server and client are provided.
While used in production at DAMEDIC, this project is still in its early days and the feature set is quite limit. We will add features as we require them. We welcome external contributions.
-
FHIR® model types with JSON and XML (un)marshaling
- generated from the FHIR® specification
var r r4.ContainedResource // container class because json.Unmarshal can not unmarshal directly into interfaces err := json.Unmarshal(data, &r) ... = r.Resource // the actual resource of type model.Resource
-
Extensible REST API with capabilities modeled as interfaces for building server
- Capability detection by runtime
reflectiontype assertion (see Capabilities)- alternatively: generic API for building adapters
- automatic generation of
CapabilityStatements
with full SearchParameter integration
- Interactions:
create
,read
,update
,delete
,search
,$operations
(see Roadmap for the remaining interactions) - Advanced search parameter handling with full SearchParameter resource support
- Cursor-based pagination
- Capability detection by runtime
-
Fully typed client implementation
- Interactions:
create
,read
,update
,delete
,search
,$operations
- Interactions:
-
FHIRPath evaluation
-
FHIRPath v2.0.0 specification; except full UCUM support
-
-
R4, R4B & R5
use build tags
r4
,r4b
orr5
for conditional compilation if you only need runtime support for specific versions
A quick "getting started" tutorial for server-side can be found in the ./examples/demo
project.
./examples/client
shows how the client can be used.
You can find more examples in ./examples/
.
The mock
example shows how to build custom FHIR® facades on top of legacy data sources
using the concrete capabilities API, including the required CapabilityBase
implementation.
The proxy
example uses the generic API to forward all requests to another FHIR® server.
go run ./examples/proxy https://server.fire.ly/
This starts a simple mock-facade that forwards all requests to a test-server.
From another terminal, run
curl 'http://localhost/Patient/547'
or
curl 'http://localhost/Patient?_id=547'
to get a bundle.
Everything is designed around capabilities, represented by interfaces (e.g. PatientSearch
).
This flexible architecture allows different use cases, such as
- building FHIR® facades to legacy systems by implementing a custom backend
- using this library as a FHIR® client (by leveraging a - still to be build - REST backend)
The library provides two API styles. The concrete API:
func (a myAPI) ReadPatient(ctx context.Context, id string) (r4.Patient, error) {}
func (a myAPI) SearchPatient(ctx context.Context, options search.Options) (search.Result, error) {}
func (a myAPI) SearchCapabilitiesPatient(ctx context.Context) (search.Capabilities[r4.SearchParameter], error) {}
and the generic API:
func (a myAPI) Read(ctx context.Context, resourceType, id string) (r4.Patient, error) {}
func (a myAPI) Search(ctx context.Context, resourceType string, options search.Options) (search.Result, error) {}
func (a myAPI) CapabilityStatement(ctx context.Context) (r5.CapabilityStatement, error) {}
You can implement your custom backend or client either way.
The concrete API is ideal for building custom FHIR® facades where a limited set of resources is used (see
./examples/mock
).
The generic API is better suited for e.g. building FHIR® clients (see
./examples/proxy
)
or standalone FHIR® servers.
FHIR operations are supported at system, type, and instance levels.
You can expose operations using the concrete API by providing XyzOperationDefinition
methods and matching InvokeXyz
methods.
The REST server auto-discovers these and surfaces them in the CapabilityStatement.
// OperationDefinition declarations
func (b *backend) PingOperationDefinition() r5.OperationDefinition {
return r5.OperationDefinition{Id: &r5.Id{Value: ptr.To("ping")}, Code: r5.Code{Value: ptr.To("ping")}, System: r5.Boolean{Value: ptr.To(true)}}
}
func (b *backend) EchoOperationDefinition() r5.OperationDefinition {
return r5.OperationDefinition{Id: &r5.Id{Value: ptr.To("echo")}, Code: r5.Code{Value: ptr.To("echo")}, Type: r5.Boolean{Value: ptr.To(true)}, Resource: []r5.Code{{Value: ptr.To("Patient")}}}
}
func (b *backend) HelloOperationDefinition() r5.OperationDefinition {
return r5.OperationDefinition{Id: &r5.Id{Value: ptr.To("hello")}, Code: r5.Code{Value: ptr.To("hello")}, Instance: r5.Boolean{Value: ptr.To(true)}, Resource: []r5.Code{{Value: ptr.To("Patient")}}}
}
// Invoke methods
func (b *backend) InvokePing(ctx context.Context, params r5.Parameters) (r5.Parameters, error) { return r5.Parameters{}, nil }
func (b *backend) InvokeEcho(ctx context.Context, resourceType string, params r5.Parameters) (r5.Patient, error) { return r5.Patient{}, nil }
func (b *backend) InvokeHello(ctx context.Context, resourceType, id string, params r5.Parameters) (r5.Parameters, error) { return r5.Parameters{}, nil }
With these, the server exposes:
GET|POST /$ping
GET|POST /Patient/$echo
GET|POST /Patient/{id}/$hello
And lists them in the CapabilityStatement (ping under rest.operation
, echo/hello under Patient in rest.resource[].operation
).
// Using R4/R5 client wrappers
res, err := client.InvokeSystem(ctx, "ping", r5.Parameters{})
res, err := client.InvokeType(ctx, "Patient", "echo", r5.Parameters{ /* name, etc. */ })
res, err := client.InvokeInstance(ctx, "Patient", "123", "hello", r5.Parameters{})
// Generic interface
res, err := generic.Invoke(ctx, "", "", "ping", params)
res, err := generic.Invoke(ctx, "Patient", "", "echo", params)
res, err := generic.Invoke(ctx, "Patient", "123", "hello", params)
In addition to InvokeSystem
, InvokeType
, and InvokeInstance
, the client exposes generated convenience methods for spec-defined operations:
-
System-level:
InvokeXxx(ctx, params)
calls/$xxx
, e.g.:// R4 terminology and conformance examples res, err := client.InvokeVersions(ctx, r5.Parameters{}) // /$versions res, err := client.InvokeClosure(ctx, r5.Parameters{}) // /$closure
-
Type/Instance-level:
Invoke{Resource}Xxx(ctx, params, id ...string)
calls/{type}/$xxx
or/{type}/{id}/$xxx
depending on whetherid
is provided, e.g.:// Patient $everything res, err := client.InvokePatientEverything(ctx, r5.Parameters{}) // /Patient/$everything res, err := client.InvokePatientEverything(ctx, r5.Parameters{}, "123") // /Patient/123/$everything // ValueSet $expand res, err := client.InvokeValueSetExpand(ctx, r5.Parameters{}) // /ValueSet/$expand // Observation $lastn (type) res, err := client.InvokeObservationLastn(ctx, r5.Parameters{}) // /Observation/$lastn
These helpers are derived from the HL7 FHIR operations list for each release (R4, R4B, R5) during code generation and are available alongside other generated client methods.
Important: When using the concrete API, you must implement the CapabilityBase
method:
func (a myAPI) CapabilityBase(ctx context.Context) (r5.CapabilityStatement, error) {
return r5.CapabilityStatement{
Status: r5.Code{Value: ptr.To("active")},
Kind: r5.Code{Value: ptr.To("instance")},
Implementation: &r5.CapabilityStatementImplementation{
Description: r5.String{Value: ptr.To("My FHIR Server")},
Url: &r5.Url{Value: ptr.To("https://my-server.com")},
},
// ... other metadata
}, nil
}
This base CapabilityStatement is enhanced with the capabilities detected from your concrete implementation.
The implementation.url
field is required as it's used to generate canonical URLs for SearchParameter references
and other fully qualified ids, e.g. in bundles.
The library automatically aggregates SearchParameter resources from your concrete implementations into the CapabilityStatement. By default, SearchParameter resources are gathered in the following way:
// Your backend implements Patient search capabilities
func (b MyBackend) SearchCapabilitiesPatient(ctx context.Context) (r4.SearchCapabilities, error) {
return r4.SearchCapabilities{
Parameters: map[string]r4.SearchParameter{
"_id": {Type: r4.SearchParamTypeToken},
"name": {Type: r4.SearchParamTypeString},
"birthdate": {Type: r4.SearchParamTypeDate},
},
}, nil
}
// The system automatically creates SearchParameter resources for these parameters
// Available at: GET /SearchParameter/Patient-name, /SearchParameter/Patient-birthdate, etc.
Note: The values of the parameters map can actually be fully specified SearchParameter resources as defined in the standard. If you omit certain (required) fields like in the above example, these get augmented by the framework.
Attention: If you implement
SearchParameterSearch
(thesearch
interaction for theSearchParameter
resource), you will overwrite the automatic gathering and augmentation as described above.
Wrapper structs facilitate interoperability between the generic and the concrete API.
genericAPI := capabilitiesR4.Generic{Concrete: concreteAPI}
and vice versa:
concreteAPI := capabilitiesR4.Concrete{Generic: genericAPI}
The FHIRPath v2.0.0 specification is implemented with the exception of full UCUM support. For quantity comparisons and operations, the unit is only asserted for equality.
From the additional functions defined in the FHIR specification, only
extension(url : string) : collection
is implemented.
Mostly, because these require validation which is not implemented by fhir-toolbox-go
, yet.
For a quick usaage example see ./examples/fhirpath
.
The FHIRPath evaluation engine uses apd.Decimal under the hood. Precision of decimal operations can be set by supplying an apd.Context
// Setup context
ctx := r4.Context()
// with defined precision for decimal operations.
ctx = fhirpath.WithAPDContext(ctx, apd.BaseContext.WithPrecision(100))
expr, err := fhirpath.Parse("Observation.value / 3")
if err != nil {
// Handle error
}
// Evaluate the expression against a FHIR resource
result, err := fhirpath.Evaluate(r4.Context(), observation, expr)
if err != nil {
// Handle error
}
Attention: By default the precision is set to
0
.
The FHIRPath implementation is tested against the FHIRPath test suite.
Tests are downloaded on first execution and cached afterward into the build
folder.
As the test cases XML has some inconsistencies and features not supported yet,
the tests are modified before execution in fhirpath/fhirpath_test.go
- interactions
- support for resource versioning (
vread
,history
) - at some point
patch
andbatch/transaction
, but no priority at the moment
- support for resource versioning (
- constants for code systems and/or value-sets
- adapter for resolving
_include
and_revinclude
- validation of resources (also against profiles)
Package | Description |
---|---|
model |
Generated FHIR® model types |
capabilities/.. |
Interfaces modeling capabilities a server can provide or a client can consume |
capabilites/search |
Types and helper functions for implementing search capabilities |
fhirpath |
FHIRPath execution engine |
rest |
FHIR® REST server implementation |
testdata |
Utils for loading test data and writing tests |
examples |
Examples on what you can do with this module |
Everything part of the FHIR® specification is in scope of this project. However, we (DAMEDIC) do not strive for feature-completion. Instead we will only implement what we need for building our products. See Contribution below.
We are happy to accept contributions. Bugfixes are always welcomed. For more elaborate features we appreciate commitment to maintain the contributed code.