GraphQL API
OctoFHIR provides a GraphQL API following the GraphQL for FHIR specification. It offers a flexible alternative to the REST API for querying and mutating FHIR resources.
Quick Start
Section titled “Quick Start”-
Enable GraphQL in configuration
octofhir.toml [graphql]enabled = trueintrospection = true # Disable in production -
Get an access token
Terminal window TOKEN=$(curl -s -X POST http://localhost:8888/oauth/token \-d "grant_type=password&username=admin&password=admin123" \| jq -r '.access_token') -
Query a resource
Terminal window curl http://localhost:8888/\$graphql \-H "Authorization: Bearer $TOKEN" \-H "Content-Type: application/json" \-d '{"query": "{ PatientList { id name { given family } } }"}'
Endpoints
Section titled “Endpoints”| Endpoint | Method | Description |
|---|---|---|
/$graphql | POST | System-level GraphQL endpoint |
/$graphql | GET | Query via query URL parameter |
/{Type}/{id}/$graphql | POST | Instance-level (resource in context) |
Query Operations
Section titled “Query Operations”Read Single Resource
Section titled “Read Single Resource”Query a resource by ID:
query { Patient(_id: "patient-123") { id meta { versionId lastUpdated } name { given family } birthDate gender }}Search Resources (List)
Section titled “Search Resources (List)”Search with FHIR search parameters:
query { PatientList( name: "Smith" gender: "female" _count: 10 _sort: "-birthDate" ) { id name { given family } birthDate }}Connection (Cursor Pagination)
Section titled “Connection (Cursor Pagination)”For large result sets, use connection-style pagination:
query { PatientConnection(first: 10, after: "cursor123") { pageInfo { hasNextPage hasPreviousPage startCursor endCursor } edges { cursor node { id name { family } } } totalCount }}Nested Resources (References)
Section titled “Nested Resources (References)”Resolve references automatically:
query { ObservationList(code: "29463-7") { id code { coding { system code display } } valueQuantity { value unit } subject { reference resource { ... on Patient { id name { given family } } } } performer { resource { ... on Practitioner { id name { given family } } } } }}Reverse References
Section titled “Reverse References”Find resources that reference a given resource:
query { Patient(_id: "patient-123") { id name { family }
# All observations for this patient ObservationList(_reference: "subject") { id code { text } effectiveDateTime }
# All conditions for this patient ConditionList(_reference: "subject") { id code { text } clinicalStatus { coding { code } } } }}Mutation Operations
Section titled “Mutation Operations”Create Resource
Section titled “Create Resource”mutation { PatientCreate(res: { resourceType: "Patient" name: [{ given: ["John"] family: "Doe" }] gender: "male" birthDate: "1990-01-15" }) { id meta { versionId lastUpdated } name { given family } }}Update Resource
Section titled “Update Resource”mutation { PatientUpdate( id: "patient-123" res: { resourceType: "Patient" id: "patient-123" name: [{ given: ["John", "William"] family: "Doe" }] gender: "male" birthDate: "1990-01-15" } ) { id meta { versionId } name { given family } }}Delete Resource
Section titled “Delete Resource”mutation { PatientDelete(id: "patient-123") { id }}Subscriptions
Section titled “Subscriptions”Configuration
Section titled “Configuration”[graphql]enabled = truesubscriptions = trueAvailable Subscriptions
Section titled “Available Subscriptions”subscription { # All resource changes resourceChanged(resourceType: "Patient") { eventType # CREATED, UPDATED, DELETED resourceType resourceId resource { ... on Patient { id name { family } } } }}subscription { # Specific event types resourceCreated(resourceType: "Observation") { resourceId resource { ... on Observation { code { text } valueQuantity { value unit } } } }}Search Parameters
Section titled “Search Parameters”GraphQL uses the same search parameters as the REST API:
Common Parameters
Section titled “Common Parameters”| Parameter | Description |
|---|---|
_id | Resource ID |
_lastUpdated | Last modification time |
_count | Results per page |
_offset | Skip first N results |
_sort | Sort order (prefix - for descending) |
Resource-Specific Parameters
Section titled “Resource-Specific Parameters”query { # Patient search parameters PatientList( name: "John" family: "Doe" birthdate: "ge1990-01-01" gender: "male" identifier: "MRN|12345" ) { id }
# Observation search parameters ObservationList( code: "29463-7" date: "ge2024-01-01" patient: "Patient/123" status: "final" value_quantity: "gt100" ) { id }}Modifiers
Section titled “Modifiers”Search modifiers work the same as REST:
query { PatientList( name_exact: "John Smith" # :exact modifier name_contains: "smith" # :contains modifier deceased_missing: false # :missing modifier ) { id name { text } }}Configuration Reference
Section titled “Configuration Reference”[graphql]# Enable GraphQL endpointsenabled = true
# Maximum query depth (prevents deeply nested queries)max_depth = 15
# Maximum query complexity scoremax_complexity = 500
# Enable schema introspection (disable in production)introspection = true
# Enable real-time subscriptionssubscriptions = false
# Allow batched queriesbatching = false
# Maximum operations per batchmax_batch_size = 10Error Handling
Section titled “Error Handling”GraphQL errors follow the standard format:
{ "data": null, "errors": [ { "message": "Resource not found", "locations": [{ "line": 2, "column": 3 }], "path": ["Patient"], "extensions": { "code": "NOT_FOUND", "resourceType": "Patient", "resourceId": "invalid-id" } } ]}Common Error Codes
Section titled “Common Error Codes”| Code | Description |
|---|---|
NOT_FOUND | Resource does not exist |
UNAUTHORIZED | Missing or invalid token |
FORBIDDEN | Access denied by policy |
VALIDATION_ERROR | Invalid resource data |
COMPLEXITY_EXCEEDED | Query too complex |
DEPTH_EXCEEDED | Query too deeply nested |
Best Practices
Section titled “Best Practices”1. Request Only Needed Fields
Section titled “1. Request Only Needed Fields”# Good - only request what you needquery { PatientList { id name { family } }}
# Avoid - requesting everythingquery { PatientList { id meta name ... }}2. Use Fragments for Reusable Selections
Section titled “2. Use Fragments for Reusable Selections”fragment PatientBasics on Patient { id name { given family } birthDate gender}
query { patient1: Patient(_id: "p1") { ...PatientBasics } patient2: Patient(_id: "p2") { ...PatientBasics }}3. Use Variables for Dynamic Values
Section titled “3. Use Variables for Dynamic Values”query GetPatient($id: String!) { Patient(_id: $id) { id name { given family } }}{ "query": "query GetPatient($id: String!) { ... }", "variables": { "id": "patient-123" }}4. Paginate Large Result Sets
Section titled “4. Paginate Large Result Sets”query { PatientConnection(first: 50) { pageInfo { hasNextPage endCursor } edges { node { id name { family } } } }}Comparison with REST
Section titled “Comparison with REST”| Feature | REST | GraphQL |
|---|---|---|
| Endpoint | Multiple per resource | Single /$graphql |
| Data fetching | Fixed response shape | Client specifies fields |
| Related data | _include/_revinclude | Nested queries |
| Batch requests | Transaction bundle | Multiple queries |
| Real-time | Subscriptions (planned) | Subscriptions |
| Caching | HTTP caching | Client-side |
When to Use GraphQL
Section titled “When to Use GraphQL”- Complex data requirements - Need data from multiple related resources
- Mobile/bandwidth-sensitive - Request only needed fields
- Interactive applications - Explore schema with introspection
- Real-time updates - Use subscriptions
When to Use REST
Section titled “When to Use REST”- Simple CRUD - Standard create/read/update/delete
- Bulk operations - Transactions, batch imports
- HTTP caching - Leverage proxy caches
- Tooling compatibility - Wide ecosystem support
Examples
Section titled “Examples”Patient with All Observations
Section titled “Patient with All Observations”query PatientWithObservations($patientId: String!) { Patient(_id: $patientId) { id name { given family } birthDate
ObservationList(_reference: "subject", _sort: "-date", _count: 10) { id code { coding { system code display } text } effectiveDateTime valueQuantity { value unit } status } }}Create Observation with Patient Reference
Section titled “Create Observation with Patient Reference”mutation CreateVitalSign($patientId: String!, $value: Decimal!) { ObservationCreate(res: { resourceType: "Observation" status: "final" category: [{ coding: [{ system: "http://terminology.hl7.org/CodeSystem/observation-category" code: "vital-signs" }] }] code: { coding: [{ system: "http://loinc.org" code: "29463-7" display: "Body Weight" }] } subject: { reference: $patientId } effectiveDateTime: "2024-01-15T10:30:00Z" valueQuantity: { value: $value unit: "kg" system: "http://unitsofmeasure.org" code: "kg" } }) { id meta { versionId lastUpdated } }}Search with Multiple Criteria
Section titled “Search with Multiple Criteria”query ActiveDiabeticPatients { ConditionList( code: "http://snomed.info/sct|73211009" clinical_status: "active" _count: 100 ) { id subject { resource { ... on Patient { id name { given family } birthDate
# Get their recent lab results ObservationList( _reference: "subject" category: "laboratory" _sort: "-date" _count: 5 ) { code { text } valueQuantity { value unit } effectiveDateTime } } } } }}