This FHIR server implements support for querying FHIR data using GraphQL(https://graphql.org/).
You can access the GraphQL playground by going to the /$graphql url in your browser e.g., http://fhir.dev.bwell.zone/$graphql. This will redirect you to the OAuth provider to login and then will store your JWT token in a cookie so you can use the Playground.
You can use the standard GraphQL client libraries or Postman and access the /$graphql url. You will need to pass the OAuth token as a Bearer token to authenticate. See https://github.com/icanbwell/fhir-server/blob/master/security.md for details.
All the GraphQL entities and properties have inline documentation from FHIR specifications
query {
practitionerRole {
entry {
resource {
id
practitioner {
name {
family
given
}
}
organization {
name
}
healthcareService {
name
}
location {
name
}
}
}
}
}
import requests
import json
url = "https://fhir.dev.bwell.zone/$graphql"
payload="{\"query\":\"query {\\n entry {\\n resource {\\n practitionerRole {\\n id\\n practitioner {\\n name {\\n family\\n given\\n }\\n }\\n organization {\\n name\\n }\\n healthcareService {\\n name\\n }\\n location {\\n name\\n }\\n }\\n}\\n}\\n}\",\"variables\":{}}"
headers = {
'Authorization': 'Bearer {put token here}',
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
var https = require('follow-redirects').https;
var fs = require('fs');
var options = {
method: 'POST',
hostname: 'fhir.dev.bwell.zone',
path: '/$graphql',
headers: {
Authorization: 'Bearer {put token here}',
'Content-Type': 'application/json',
},
maxRedirects: 20,
};
var req = https.request(options, function (res) {
var chunks = [];
res.on('data', function (chunk) {
chunks.push(chunk);
});
res.on('end', function (chunk) {
var body = Buffer.concat(chunks);
console.log(body.toString());
});
res.on('error', function (error) {
console.error(error);
});
});
var postData = JSON.stringify({
query: `query {
entry {
resource {
practitionerRole {
id
practitioner {
name {
family
given
}
}
organization {
name
}
healthcareService {
name
}
location {
name
}
}
}
}
}`,
variables: {},
});
req.write(postData);
req.end();
curl --location --request POST 'https://fhir.dev.bwell.zone/$graphql' \
--header 'Authorization: Bearer {put token here}' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"query {\n entry {\n resource {\n practitionerRole {\n id\n practitioner {\n name {\n family\n given\n }\n }\n organization {\n name\n }\n healthcareService {\n name\n }\n location {\n name\n }\n }\n}\n}\n}","variables":{}}'
Querying union types require the following syntax (https://www.apollographql.com/docs/apollo-server/schema/unions-interfaces/#querying-a-union):
provider {
__typename
... on Organization {
id
identifier {
system
value
}
}
... on Practitioner {
id
name {
family
given
}
}
}
We use the apollo-server-express framework to implement the GraphQL middleware. This is implemented in https://github.com/icanbwell/fhir-server/blob/master/src/middleware/graphqlServer.js
We use the graphql-tools framework, so we can store the schema and resolver for each FHIR entity in a separate file and then merge them together to create the full schema.
For security, we use the same mechanism for both REST and GraphQL. There are two pieces to this:
- The middleware to read auth tokens, decrypt them and add
request.user
andrequest.scope
: https://github.com/icanbwell/fhir-server/blob/master/src/strategies/jwt.bearer.strategy.js - code to check these permissions when needed. This code is stored in the https://github.com/icanbwell/fhir-server/tree/master/src/operations
We use a code generator to read the FHIR schema and generate the GraphQL schema and resolvers. This code generator is in https://github.com/icanbwell/fhir-server/blob/master/src/fhir/generator/generate_graphql_classes.py and can be run by typing the command make graphql
.
In the https://github.com/icanbwell/fhir-server/tree/master/src/graphql/schemas folder each FHIR entity has its own GraphQL schema file. The schema.graphql file is the top level schema element. In the https://github.com/icanbwell/fhir-server/tree/master/src/graphql/resolvers folder each FHIR resource has its own GraphQL resolver file. The resolvers.js merges all the resolvers together.
This FHIR server automatically turns each reference into a nested access to the referenced resource.
To add a reverse link:
- Add a custom schema file (e.g., https://github.com/icanbwell/fhir-server/blob/master/src/graphql/schemas/custom/patient.graphql)
- Add a custom resolver file (e.g., https://github.com/icanbwell/fhir-server/blob/master/src/graphql/resolvers/custom/patient.js)
The FHIR server will automatically load these the next time it runs.
Sometimes you want to add enrichment to the underlying FHIR data (e.g., calculating totals, adding additional properties etc). To enable this we have a concept of pluggable enrichment providers.
To add a new enrichment provider:
- Add a new provider class here: https://github.com/icanbwell/fhir-server/tree/master/src/enrich/providers. You can see examples in here. Implement the interface.
- Register your new provider in https://github.com/icanbwell/fhir-server/tree/master/src/createContainer.js Now this enrichment provider will be run for every resource and can add additional properties. These properties are available both when accessing the server via REST or GraphQL.
value
[Below example uses nestedvalue
field. Other nested options are:system
,code
¬Equals
]
query {
observation(
status: {
value: {
value: "completed"
}
}
) {
entry {
resource {
id
}
}
}
}
values
[List format ofvalue
]
query {
observation(
status: {
values: [
{
value: "completed"
}
]
}
) {
entry {
resource {
id
}
}
}
}
notEquals
[Below example uses nestedvalue
field. Other nested options are:system
,code
&value
]
query {
observation(
status: {
notEquals: {
value: "completed"
}
}
) {
entry {
resource {
id
}
}
}
}
missing
query {
observation(
status: {
missing: true
}
) {
entry {
resource {
id
}
}
}
}
value
query {
person(
name: {
value: "test"
}
) {
entry {
resource {
id
}
}
}
}
values
query {
person(
name: {
values: ["test1", "test2"]
}
) {
entry {
resource {
id
}
}
}
}
notEquals
[Below example uses nestedvalue
field. Other nested option is:values
which is just list ofvalue
]
query {
person(
name: {
notEquals: {
value: "testing"
}
}
) {
entry {
resource {
id
}
}
}
}
missing
query {
person(
name: {
missing: true
}
) {
entry {
resource {
id
}
}
}
}
value
query {
procedure(
encounter: {
value: "5abd4446-938e-40ab-b5f2-50c3f74cfa73"
}
) {
entry {
resource {
id
}
}
}
}
target
[To be used withvalue
]
query {
procedure(
encounter: {
target: "Encounter",
value: "5abd4446-938e-40ab-b5f2-50c3f74cfa73"
}
) {
entry {
resource {
id
}
}
}
}
notEquals
[Also supportstarget
insidenotEquals
]
query {
procedure(
encounter: {
notEquals: {
value: "5abd4446-938e-40ab-b5f2-50c3f74cfa73"
}
}
) {
entry {
resource {
id
}
}
}
}
missing
query {
procedure(
encounter: {
missing: true
}
) {
entry {
resource {
id
}
}
}
}
value
[Supported operations on Date/DateTime are:equals
,notEquals
,greaterThan
,greaterThanOrEqualTo
,lessThan
,lessThanOrEqualTo
]
query {
immunization(
date: {
# Below filter does an AND operation of all items in "value"
value: {
greaterThan: "2021-01-01",
lessThan: "2022-01-01"
}
}
) {
entry {
resource {
id
}
}
}
}
values
[List format ofvalue
]
query {
immunization(
date: {
# Below filters does an OR operation of all items in "values"
values: [
{ lessThan: "2021-01-01" }
{ greaterThan: "2022-01-01" }
]
}
) {
entry {
resource {
id
}
}
}
}
missing
query {
immunization(
date: {
missing: true
}
) {
entry {
resource {
id
}
}
}
}
In GraphQLv2, the resources are now returned as a FHIR Bundle.
GraphQLv1:
query {
practitionerRole {
id
}
}
Graphqlv2:
query {
practitionerRole {
entry {
resource {
id
}
}
}
}
Pass the header handling=strict
to enable strict variable validation. The FHIR Server will return a validation error if the query variables present in the GraphQL query do not have corresponding values in the request. Default value of the header is lenient
skipping this validation. This is in addition to Enabling Strict Validation in Search Requests.