Publish-box (todo)
FHIR Infrastructure Work Group | Maturity Level: 3 | Standards Status: Trial Use |
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. This page describes how to use GraphQL with FHIR. The GraphQL interface may be implemented by any server, and may be provided as a facade service in front of a conformant RESTful API. For GraphQL documentation, see http://graphql.org .
Implementation Note: This page is a draft. For ongoing discussion, see chat.fhir.org .
Implementers should note that GraphQL is not a formal standard, and no standards path has been described for it. At present, GraphQL is copyright by Facebook , and licensed under a standard BSD-3 license. The GraphQL language is still under development.
The graphQL functionality defined here has a matching schema.
The standard end points for graphQL are as defined on the $graphql operation:
[base]/$graphql
A System level query. The query must start by selecting a resource type and some search criteria, as defined below.
[base]/[Type]/[id]/$graphql
A Resource Instance level the query assumes a single resource is in scope, and just queries on the data available in the given resource.
GraphQL can be invoked by get with a query parameter, or a POST with the graphQL as the body, or a JSON body. (see the GraphQL documentation ).
The mime type of the response is application/json. Other formats are not described by the specification. The mimetype is *not* application/fhir+json - the response is not a FHIR resource, though it may look like one closely.
Servers that conform to FHIR/GraphQL specification:
Note that most servers only provide a subset of the full functionality described by the FHIR interface. Where servers support FHIR features, and implement GraphQL, they must make them available as defined by this page, but these rules do not require that servers implement all (or even any) of the functionality defined here. Also, these rules do not prevent servers providing additional end-points, queries or mutations that provide additional functionality.
Clients that use the FHIR/GraphQL specification make requests using graphQL statements that select subsets of the resources to retrieve. Clients SHOULD always be retrieving mandatory and modifier elements and checking their values (see the safety checklist).
With regard to handling errors, FHIR GraphQL servers:
A graphQL error response with an OperationOutcome looks like this:
{ "data": null, "errors": [ { "extensions": { "resource" : { "resourceType": "OperationOutcome", "issue": [ { "severity": "error", "code": "exception", "diagnostics": "500: Internal server error" } ] }, }, "message": "500: Internal server error" } ] }
The error response SHALL include at least one error.
Each error SHALL include a message
, and MAY include the graphQL locations
and path
properties.
Each error SHOULD include an OperationOutcome in the resource
property in the graphQL extensions
element.
Note that in this usage, there MAY be multiple OperationOutcome, one for each errors
item, with one issue matching the message.
Any FHIR defined field can be used directly e.g. this graphql against the Patient resource (r3)
{ name { text given family } }
Example: http://test.fhir.org/r5/Patient/example/$graphql?query={name{text,given,family}} (note: examples are only informative).
Polymorphic fields are represented by their JSON property name E.g. for Observation.value[x]:
{ valueQuantity { value unit } }
Example: http://test.fhir.org/r5/Observation/example/$graphql?query={valueQuantity{value,unit}} (note: examples are only informative).
Note: This is because the leaf names have to correspond to scalar types, so there is no use selecting all the variants at once
Extensions on primitives: the JSON convention for primitives is observed. e.g. use _[name] for accessing extensions on primitives. So
{ birthDate _birthDate { extension {valueDateTime} } }
results in
{ "birthDate":"2016-05-18", "_birthDate":{ "extension":[{ "valueDateTime":"2016-05-18T10:28:45Z" }] } }
Example: http://test.fhir.org/r5/Patient/example/$graphql?query={birthDate,_birthDate{extension{valueDateTime}}} (note: examples are only informative).
Primitive fields SHALL NOT have any arguments at all. Complex fields may have one of more of the following parameters, all of which help select a subset of a repeating element:
fhirpath
- a FHIRPath statement selecting which of the subnodes is to be included[field]
- the name of a sub-property with a specified value that must be matched for the field to be included_offset
- specify the offset to start at for a repeating element (see below)_count
- specify how many to elements to return from a repeating listFHIRPath:
Use a FHIRPath statement selecting which of the set of notes in a list is to be included:
{ name(fhirpath: "family.exists()") { text given family } }
Example: http://test.fhir.org/r5/Patient/example/$graphql?query={name(fhirpath:%22family.exists()%22){text,given,family}} (as compared to http://test.fhir.org/r5/Patient/example/$graphql?query={name{text,given,family}} ) (note: examples are only informative).
Field Filter:
Not all systems support FHIRPath, so a simpler syntax is provided, where the client specifies a single sub-element by name, and a specified value that must be matched for the field to be included:
{ name(use: official) { text given family } }
Example: http://test.fhir.org/r5/Patient/example/$graphql?query={name(use:official){text,given,family}} (note: examples are only informative).
One way this can be used is to select particular extensions:
{ myext: extension(url: "http://myextension.url/details") { value : valueString } }
will result in
{ "myext" : { "value" : "some value" } }
List offsets: _offset and _count
For some large resources, it may be desired to select only a subset of a long list of elements, and
to make repeated requests reading further into the list as long as required. This can be done with the
_count
and offset
filters:
{ entry(_count: 5, _offset: 5) { deleted item { reference } } }
Example: http://test.fhir.org/r5/List/long/$graphql?query={entry(_count:5,_offset:5){deleted item{reference}}} (note: examples are only informative).
This will select the second set of 5 entries in the nominated List resource. Clients can use this approach to iterate through a list. If a filter is applied to this (as documented above), the offset and count apply to the filtered list. Clients would iterate the list until there are no more entries returned.
An object of type Reference can have an additional selection resource
. This is an instruction to the server to resolve the reference, and then include the contents of the resource as specified by sub-selections in the property name "resource" (can be aliased). e.g. On Observation:
{ subject { reference, resource {active} } }
The resource selector has two arguments:
optional
: true | false. (default is false). If the server cannot resolve the reference (e.g. resource does not exist, or user security rights or choices do not permit resource to be seen), and optional is not true, the server returns an error instead of the graph output{ id subject { reference resource { ...on Patient { birthDate } ...on Group { name } } } code {coding {system code} } }
Example: http://test.fhir.org/r5/Observation/example/$graphql?query={id,subject{reference,resource{...on%20Patient{birthDate}...on%20Practitioner{practitionerRole{speciality}}}}code{coding{system,code}}} (note: examples are only informative).
but slightly denser:
{ id subject { reference resource(type : Patient) { birthDate } resource(type : Practitioner) { practitionerRole { speciality } } } code {coding {system code} } }
Example: http://test.fhir.org/r5/Observation/example/$graphql?query={id,subject{reference,resource(type:Patient){birthDate}resource(type:Practitioner){practitionerRole{speciality}}}code{coding{system,code}}} (note: examples are only informative).
Clients can use either approach - type selection as a parameter, of using a fragment type condition - the first is shorter while the second aligns with the GraphQL schema more explicitly.
When a GraphQL statement is run at the system level, rather than against a particular resource, the first thing the query must do is select the resource(s) of interest. The logic of this follows the FHIR search API, but reimplements in a GraphQL centric way.
There are 3 ways to query, for a single resource, for a simple list of resources, and a full API. For a single resource, the client names the type of the resource, and provides an _id:
{ Patient(_id: example) { id, active } }
This returns a single Patient with the name id. The output from this is a single resource:
{ "data" : { "Patient" { "id" : "example", "active" : "true", } } }
Example: http://test.fhir.org/r5/$graphql?query={Patient(_id:example){id,name{given,family}}} (note: examples are only informative).
Alternatively, the client can ask for a list of resources. Here, the client simply asks for a list of resources:
{ ConditionList(clinical_status: relapse, patient: example) { id, clinicalStatus } }
This is a request to list all the Condition resources that have a status if 'relapsed' for the patient with id = example. The arguments are most of the search parameters defined on or for the specified resource. Notes:
The output of this is a list:
{ "data" : { "ConditionList" : [{ "id" : "100", "clinicalStatus" : "relapse" },{ "id" : "100", "clinicalStatus" : "relapse" }] } }
Example: http://test.fhir.org/r5/$graphql?query={PatientList(name:%22pet%22){name{family,given}}} (note: examples are only informative).
Servers may reject the request if there are too many matches to return in a single request. If they do so, they SHALL return an error indicating that the query could not be fulfilled. (rather than silently filtering the list).
The simple list approach does not allow for the management of long lists. To do this, clients are able to request a Connection based approach (based on http://graphql.org/learn/pagination/ , but adapted to the existing FHIR Search API).
{ ConditionConnection (clinical_status: active, patient: example) { count offset pagesize edges { mode, score, resource { id, active } } first previous next last } }
The arguments are the same as for the simple List case, with the addition of the special argument 'cursor' (see below). The server returns a connection object that contains information about the search, along with a list of 'edges', one for each match. Each edge has 3 properties: mode, score (match the same properties on Bundle.entry.search) and "resource" which is the actual matches.
{ "data" : { "ConditionConnection" : { "count": 50, "offset" : 0, "pageSize" : 25, "next" : "45f9ada8-db37-4498-ba7d-75a044668387:3" "edges" : [{ "resource" : { "id" : "100", "clinicalStatus" : "relapse", } },{ "resource" : { "id" : "100", "clinicalStatus" : "relapse", } }] } } }
Example 1: http://test.fhir.org/r5/$graphql?query={PatientConnection(name:%22pet%22){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{name{family,given}}}}} (note: examples are only informative).
Example 2 (cursor): http://test.fhir.org/r5/$graphql?query={PatientConnection(){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{resourceType,id,name{family,given}}}}}}}} (note: examples are only informative).
Notes:
The client can follow up on the first/previous/next/last links using the argument 'cursor':
{ ConditionConnection (_cursor : "45f9ada8-db37-4498-ba7d-75a044668387") { count offset pagesize edges { id, clinicalStatus } first previous next last } }
Example: http://test.fhir.org/r5/$graphql?query={PatientConnection(cursor:%225b0719b5-0f01-442e-9576-5b0514c19a:50%22){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{resourceType,id,name{family,given}}}}}}}} (note: examples are only informative). (note, though, that to make this one work, you have to replace the cursor token with one that you got from the link above).
Notes:
The specification defines the 2 forms of searches to meet differing requirements. For the List search:
In general, clients should use the simple search, for efficiency purposes, where appropriate (e.g. where the user is not going see a 'next' button). This particularly applies when doing reverse reference resolution.
It's also possible to use search is a special mode, doing reverse lookups - e.g. list all the resources that refer to this resource. An example of this use is to look up a patient, and also retrieve all the Condition resources for the patient.
This is a special case of search, above, but with an additional mandatory parameter _reference
. For example:
{ name { [some fields] } ConditionList(_reference: patient) { [some fields from Condition] } }
There must be at least the argument "_reference" which identifies which of the search parameters for the target resource is used to match the resource that has focus. In addition, there may be other arguments as defined above in search (except that the "id" argument is prohibited here as nonsensical)
The response for the query above would be
{ "name: [ [some fields] ], "ConditionList" : [ { [some fields from matching Condition resource] } ] }
Example: http://test.fhir.org/r5/Patient/example/$graphql?query={name{family,given}ConditionList(_reference:patient){id,clinicalStatus}} (note: examples are only informative).
The "connection" based search option is also supported, as described above, with the addition of _reference. If the client wishes to pursue any of the cursor based links in the graphQL results it asks for back, then it initiates this a new separate query as defined above, rather than repeating the nested query. I.e. the 'cursor' argument is prohibited, except at the root of query.
Example: http://test.fhir.org/r5/Patient/example/$graphql?query={name{family,given}ConditionConnection(_reference:patient){count,offset,pagesize,first,previous,next,last,edges{mode,score,resource{...onCondition{resourceType,id,clinicalStatus}}}}} (note: examples are only informative).
It's also possible to apply graphql to the results of other operations. This is done by adding the parameter "_graphql" when invoking the operation. e.g.
GET [base]/ValueSet/doc-typecodes/$validate-code?system=http://loinc.org&code=1963-8&display=test&_graphql={result:parameter(name:"result"){value:valueBoolean}}
Example: http://test.fhir.org/r5/ValueSet/doc-typecodes/$validate-code?system=http://loinc.org&code=1963-8&display=test&_graphql={result:parameter(name:%22result%22){value:valueBoolean}} (note: examples are only informative).
The graphQL is executed on the output of the operation. If the operation fails, and returns an operation outcome, then the graphQL is not executed.
GraphQL is a very effective language for navigating a graph and selecting subset of information from it. However for some uses, the physical structure of the result set is important. This is most relevant when extracting data for statistical analysis in languages such as R . In order to facilitate these kind of uses, FHIR servers should consider supporting the following directives that allow implementers to flatten the return graph for easier analysis
@flatten
This directive indicates that the field to which it is attached is not actually produced in the output graph. Instead, its children will be processed and added to the output graph as specified in its place.
Notes:
For an example, take this graphQL, and apply it to the patient example:
{ identifier { system value } active name { text given family } }
This will give the output:
{ "identifier": [{ "system": "urn:oid:1.2.36.146.595.217.0.1", "value": "12345" }], "active": true, "name": [{ "given": ["Peter","James"], "family": "Chalmers" },{ "given": ["Jim"] },{ "given": ["Peter","James"], "family": "Windsor" }] }
Adding the @flatten
directive changes the output:
{ identifier @flatten { system value } active name @flatten { text given family } }
This has the output:
{ "system":["urn:oid:1.2.36.146.595.217.0.1"], "value":["12345"], "active":true, "given":["Peter","James","Jim","Peter","James"], "family":["Chalmers","Windsor"] }
@first
This is a shortcut for a FHIR path filter [$index = 0] and indicates to only take the first match of the elements. Note that the selection of the first element only applies to the immediate context of the field in the source graph, not to the output graph
Example:
{ identifier @flatten { system value } active name @flatten { text given @first family } }
Gives the output:
{ "system":["urn:oid:1.2.36.146.595.217.0.1"], "value":["12345"], "active":true, "given":["Peter","Jim","Peter"], "family":["Chalmers","Windsor"] }
@singleton
This directive indicates that a field collates to a single node, not a list. It is only used in association
with fields on which a parent has @flatten
, and overrides the impact of flattening the parent in
making it a list. The server SHALL return an error if there is more than one value when flattening
Extending the previous example, adding @singleton
:
{ identifier @flatten { system @singleton value @singleton } active name @flatten @first { text given family @singleton } }
Gives the output:
{ "system":"urn:oid:1.2.36.146.595.217.0.1", "value":"12345", "active":true, "given":["Peter","James"], "family":"Chalmers" }
@slice(fhirpath)
This indicates that in the output graph, each element in the source will have "." and the result of the FHIRPath as a string appended to the specified name. This slices a list up into multiple single values. For example
{ name @slice(path: "$index") @flatten {given @first @singleton family}}
For a resource that has 2 names will result in the output
{ "Given.0" : "first name, first given", "Family.0" : ["first name family name"], "Given.1" : "second name, first given", "Family.1" : ["second name family name"] }
Other uses might be e.g. Telecom @slice(use) to generate telecom.home for instance.
Notes:
@singleton
Examples:
{ identifier @flatten { system value } active name @flatten @slice(path: "use") { given family @singleton } }
produces
{ "system":["urn:oid:1.2.36.146.595.217.0.1"], "value":["12345"], "active":true, "given.official":["Peter","James"], "family.official":"Chalmers", "given.usual":["Jim"], "given.maiden":["Peter","James"], "family.maiden":"Windsor" }
and
{ identifier @flatten { system value } active name @flatten @slice(path: "$index") { given family @singleton } }
produces
{ "system":["urn:oid:1.2.36.146.595.217.0.1"], "value":["12345"], "active":true, "given.0":["Peter","James"], "family.0":"Chalmers", "given.1":["Jim"], "given.2":["Peter","James"], "family.2":"Windsor" }
Mutations are defined for the operations Create, Update, and Delete.
Create:
PatientCreate(res : Patient) { returns a Patient }
Like the API, the Patient resource might or might not have an ID; if it is present, the value is overwritten by the server.
Update:
PatientUpdate(id : ID, res : Patient) { returns a Patient }
Delete:
PatientDelete(id : ID) { [no return] }
The parameters are input types that are resources that exactly match the output types. If these operations fail, an http error is returned.
Todo: mutations for patch, batch, transaction?