Blog

Search in FHIR and Vonk

A shed in your backyard, a closet in your spare room, the balcony on the north that you never use anyway. Everyone has it. That place where ‘stuff’ is stored. And exactly how valuable is that stuff if you can’t find it anymore? That is what search in FHIR is for. And since not everyone gets the concept right from the spec, we’ll go over some examples using Firely’s FHIR Server Vonk. We will even go as far as defining your own search parameter!

The other day l was asked: “I’m trying to do a fhirpath query on the Vonk server deployed locally.  When I try almost any fhirpath query on Patient it returns the full list of patients.  Is there something wrong with my syntax?”

GET localhost:8080/Patient?_format=json&Patient.where(id = ‘4342418’)

It was then that I decided to write this blogpost. Because the fhirpath syntax is actually correct. But the understanding of the search part of the FHIR RESTful API is wrong. In short:

  • FHIR search is based on search parameters that are defined upfront.
  • A search parameter is defined in terms of fhirpath
  • Many search parameters are defined as part of the FHIR specification
  • If your query is not covered by those, you can define your own search parameter, add it to the server and then use it in the search API.

In the subsequent examples I will use the placeholder <vonk> as the base address for the Vonk FHIR server. You can substitute it for https://vonk.fire.ly to use the public test server or localhost:<port> if you run it locally (download your evaluation copy here). All examples work in both STU3 and R4. Vonk supports both, just use the correct fhirVersion mediatype parameter to select your preferred version. E.g.:

PUT <vonk>/Patient/123
Accept=application/fhir+json;fhirVersion=4.0
Content-Type=application/fhir+json;fhirVersion=4.0
Body
{
   ...
}

Search with search parameters

We will analyze what was wrong with the original query and look at the correct way to do the search. But first we need to create a Patient resource that matches the query so we get some result. We’ll also include a Patient resource that does not match the query.

PUT <vonk>/Patient/4342418
{
"resourceType": "Patient",
"id": "4342418"
}

PUT <vonk>/Patient/abc
{
"resourceType": "Patient",
"id": "abc"
}

Analyzing errors

Now let’s issue the original query and analyze the response.

GET <vonk>/Patient?Patient.where(id = ‘4342418’)

The response will be a Bundle containing:

  • both Patients
  • an OperationOutcome stating
    "text": "Reference parameter 'Patient' is not supported."
  • a self link with
    "url": "<vonk>/Patient?_sort=-_lastUpdated&_count=10&_skip=0"

Let’s start by the latter. The specification tells us here that “In order to allow the client to be confident about what search parameters were used as criteria by the server, the server SHALL return the parameters that were actually used to process the search.” As you can see the only parameter that we tried to use is not in the self link, so apparently it was not processed by the server.

The OperationOutcome tells us why the parameter was not processed. It is not supported. Why it complains about a “Reference parameter” is for a next article.

The specification also tells us here that “In general, servers SHOULD ignore unknown or unsupported parameters for the following reasons: […]” So that explains why both Patient resources are in the result. With the single query parameter ignored, no parameters are left and all Patient resources match the query.

The correct way

The correct way to do the search from the original question is:

GET <vonk>/Patient?_id=4342418

The response will again be a Bundle. This time containing:

  • Only the Patient resource with the matching id
  • a self link with
    "url": "<vonk>/Patient?_id=4342418&_sort=-_lastUpdated&_count=10&_skip=0"

As you can see from the self link, the provided query parameter is processed by Vonk.

This query uses the predefined search parameter _id, available for every resourcetype. It is defined in the specification under the ‘Standard Parameters‘ on the search page. This definition does not show you the related fhirpath and other details of the searchparameter though. To get more details, you can simply ask Vonk:

GET <vonk>/administration/SearchParameter?code=_id

Here we are using the search API to find our searchparameter in the administration part of Vonk. It returns a Bundle containing the SearchParameter resource that defines ‘_id’.

Now inspect the contents of the searchparameter. The key parts are:

  • url: http://hl7.org/fhir/SearchParameter/Resource-id
    this is the so-called canonical url of the parameter, used to identify it across different FHIR servers and the specification itself.
  • code: _id
    this is what you use as the parametername in your search query
  • base: Resource
    this parameter is valid for the resourcetype ‘Resource’. For most parameters this contains a more specific type of resource like ‘Organization’ or ‘Patient’. Parameter can be valid for multiple types of resources, hence the array.
  • type: token
    token parameters are used for exact matches on coded values. Other types of parameters are string, number, quantity etc.
  • expression: id
    this is the actual fhirpath expression. It selects an element in the resource that will be matched against the provided argument. It is always evaluated in the context of the base, so here ‘Resource.id’.

As an exercise, update each of the Patient resources by adding a name (Fred and Bob), and then search by the Patient.name parameter:

GET <vonk>/Patient?name=Bob

Also try to find the definition of the search parameter at the administration endpoint.

Search parameters in the specification

If you want to know whether there is already a search parameter that expresses what you need in your query, you can check the specification. On every resourcetype page, at the bottom there is a list of search parameters defined for that resourcetype. Eg. http://hl7.org/fhir/R4/patient.html#search.

The common parameters, like ‘_id’ are listed at http://hl7.org/fhir/R4/search.html#all.

To find all the search parameters on a single page, visit http://hl7.org/fhir/R4/searchparameter-registry.html.

Beyond the standard search parameters

Sometimes the search parameters defined in the specification are not sufficient. A common case is when you have defined an extension to add information to a resource that is specific for your region or use case. As an example we will use an extension that is defined by the FHIR specification, but for which there is no search parameter:

https://simplifier.net/Simplifier.Core.R4.Extensions/assembly-order/~overview

This extension defines how the different parts of a HumanName must be assembled to form the full name. It is bound to a ValueSet having three values:

  • F: Prefix Family Given Suffix
  • G: Prefix Given Family Suffix
  • UNK: Unknown

Let’s update our friends Fred and Bob with this extension. And give them a Family name, otherwise the order of the names is of no meaning.

PUT <vonk>/Patient/4342418
{
	"resourceType": "Patient",
	"id": "4342418",
	"name": [
		{
			"given": ["Fred"],
			"family": "Flintstone",
			"extension": [
				{
					"url":"http://hl7.org/fhir/StructureDefinition/humanname-assembly-order",
					"valueCode": "F"
				}
			]
		}
	]
}
PUT <vonk>/Patient/abc
{
	"resourceType": "Patient",
	"id": "abc",
	"name": [
		{
			"given": ["Bob"],
			"family": "Hope",
			"extension":[
				{
					"url": "http://hl7.org/fhir/StructureDefinition/humanname-assembly-order",
					"valueCode": "G"
				}
			]
		}
	]
}

Now we would like to search for Patients that prefer their name spelled with their family name first. So that is Fred in this case. Looking at the search parameters for Patient, there is no parameter defined that selects this extension. So, we have to define one ourselves!

Defining a SearchParameter

A SearchParameter is a resource like any other in FHIR. We covered the key elements of it before. The hardest part to find out is the expression. You can use the excellent FhirPathTester tool by Brian Postlewaith (on Github and in the Windows Store) to find the right fhirpath expression. In this case we need:

Patient.name.extension.where(url='http://hl7.org/fhir/StructureDefinition/humanname-assembly-order').value

We’ll put the ‘Patient’ part in the base and the rest in the expression. Since the value in the extension is of type ‘code’, we need a search parameter of type token to search on it. And we need to choose a canonical url and a code by which to identify this parameter.

Note that we need to send the search parameter to the administration endpoint of Vonk. The resources at the administration endpoint define how Vonk behaves. If you would send it to the regular data endpoint, it would succeed but you could not use the search parameter for searching in Vonk.

PUT <vonk>/administration/SearchParameter/Patient-assembly-order
{
	"resourceType": "SearchParameter",
	"id": "Patient-assembly-order",
	"url": "http://example.org/fhir/SearchParameter/Patient-assembly-order",
	"name": "assembly-order",
	"status": "draft",
	"experimental": false,
	"publisher": "Firely Training Services",
	"code": "assembly-order",
	"base": [
		"Patient"
	],
	"type": "token",
	"description": "Value of the humanname-assembly-order extension",
	"expression": "name.extension.where(url='http://hl7.org/fhir/StructureDefinition/humanname-assembly-order').value",
}

Can you immediately search using this parameter? You can, but data that was added to the server earlier (like Fred and Bob above) will not be indexed for this parameter. So if you now try:

GET <vonk>/Patient?assembly-order=F

You will get an empty resultset. The parameter is recognized and processed, but none of the resources will match because the search index in Vonk has not been updated yet. Let’s solve that:

POST <vonk>/administration/reindex/searchparameters?include=Patient.assembly-order
Accept=application/fhir+json;fhirVersion=4.0

Note that also for this command you need to include the Accept header with a fhirVersion, otherwise the STU3 resources will be reindexed.

Vonk will respond immediately stating that it is reindexing. As long as that process is running, the search results can not be reliable so Vonk will return http code 423 ‘Locked’. Here there are only 2 resources and 1 search parameter so it will be done before you can send your query again. But in production systems with millions of resources this is something you need to plan for (or contact us). More background on reindexing is in the documentation.

Now try our query again:

GET <vonk>/Patient?assembly-order=F

And yes, we get a bundle with only Fred in it!

Recap

FHIR Search is defined in terms of predefined search parameters, not in arbitrary fhirpath statements.

You can add custom search parameters to Vonk to cover cases that are not covered by the parameters from the specification. In a custom search parameter you can use your dedicated fhirpath expression.

Further steps

Vonk does not only allow you to add your own search parameters. You can also add extensions and profiles (in the form of StructureDefinition resources) and even custom resourcetypes! And of course you don’t need to add everything by hand, just let Vonk load it from your Simplifier project, or from a public project, e.g. for your region. All of this is described as ‘Controlling the conformance resources‘ in the documentation.

Post a comment

Your email address will not be published. Required fields are marked *