Aviso
Este artigo não pretende ser um tutorial de "GraphQL on FHIR", mas sim mostrar os benefícios de considerá-lo para seus projetos e oferecer outro ponto de vista.
Quis testar como o GraphQL se comporta com o FHIR, então passei um tempo "brincando" principalmente em dois servidores:
- O servidor que usamos no Curso de HL7 FHIR ministrado pela Secretaría General del Sistema de la Integración Centroamericana (SG-SICA)
- O servidor de teste disponibilizado pelo HAPI FHIR
Mas antes de continuar e mostrar o que descobri, sinto a obrigação, por "cultura geral", de apresentar um pouco da história de como o GraphQL chegou à HL7, especificamente ao FHIR.
A partir de 2019, a HL7 decide incorporar em sua especificação do FHIR R4 o suporte ao padrão GraphQL, criado inicialmente pelo Facebook (Meta) em 2012 como resposta aos problemas de "over/under-fetching" que apareciam nos apps móveis; a ideia era unificar o acesso a dados dispersos.
O que é exatamente o GraphQL?
O GraphQL é uma linguagem de consulta de código aberto para APIs que permite aos clientes solicitar exatamente os dados de que precisam, em vez de receber dados excessivos ou incompletos. É uma alternativa mais flexível e eficiente às APIs RESTful, que resolve algumas de suas limitações, como a sobrecarga de dados. Opera com um sistema fortemente tipado que descreve os dados e permite aos clientes definir a estrutura da resposta.
Em 2015, o Facebook publica o GraphQL como projeto open source, junto com uma "reference implementation" em JavaScript, e apresenta a linguagem oficialmente. Já em 2018, a Linux Foundation anuncia a GraphQL Foundation para zelar pela especificação e promover um ecossistema neutro, com apoio de grandes players do setor. Nesse mesmo ano também se consolida o SDL (Schema Definition Language) dentro da especificação.
FHIR com GraphQL
Na versão do FHIR R4, a HL7 incorpora a "operation" $graphql como alternativa ao REST clássico, como Trial Use, com a operação padrão /$graphql e /{type}/{id}/$graphql. A operação formal $graphql está definida na especificação como ponto de entrada para executá-la em nível de sistema ou de recurso.
Em 2025, o GraphQL no FHIR ainda não é Normativo; segue como Trial Use na versão publicada (R5). O "build" contínuo do padrão também o mostra como Trial Use.
Mas mesmo em seu estado não "Normativo", recomendo utilizá-lo, já que, assim como no Facebook, evita o "over/under-fetching" e permite navegar por referências em uma única chamada — e é justamente isso que quero mostrar.
Mão na massa
Comecei a testar os servidores do mais simples ao mais complexo, então iniciei com chamadas simples. Acho que quem já passou um tempo se familiarizando com o FHIR tem o seu "olá, mundo": basicamente fazer uma consulta ao recurso Patient. Então localizei um recurso de paciente no servidor do curso de HL7 FHIR do SG-SICA que usamos, com o id 35, e o consumi:
GET http://r4.openfhir.org:8080/fhir/Patient/35
Essa consulta retornou como resposta o seguinte recurso:
{
"resourceType": "Patient",
"id": "35",
"meta": {
"versionId": "1",
"lastUpdated": "2025-09-08T14:27:35.304+00:00"
},
"identifier": [
{
"use": "official",
"system": "https://minsal.gob.sv/fhir/patient-id",
"value": "paciente-AndresKury-SV"
}
],
"active": true,
"name": [
{
"use": "official",
"family": "Kury Rivas",
"given": [
"Andres Ernesto"
]
}
],
"telecom": [
{
"system": "phone",
"value": "+503 7555 5555",
"use": "mobile"
}
],
"gender": "male",
"birthDate": "1996-07-09",
"address": [
{
"use": "home",
"text": "San Salvador, El Salvador"
}
],
"contact": [
{
"relationship": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0131",
"code": "C",
"display": "Emergency Contact"
}
]
}
],
"name": {
"family": "Rivas",
"given": [
"Evelyn"
]
},
"telecom": [
{
"system": "phone",
"value": "+503 7111 1111"
}
]
}
]
}
A execução levou 331 milissegundos, praticamente nada, então não criei muitas expectativas com consultas tão pequenas — mas mesmo assim quis testar com consumos mínimos.
Agora, aqui começa a mágica do GraphQL: não me interessa obter todas as informações que o servidor FHIR envia como resposta, e sim certos dados específicos, e é exatamente isso que o GraphQL permite:
query {
Patient(id: "35") {
id
name {
given
family
}
birthDate
gender
}
}
Aqui estou solicitando que sejam retornados o id, o nome completo (nomes e sobrenomes), a data de nascimento e o gênero. Ao fazer a consulta, o servidor FHIR devolve o seguinte:
{
"data": {
"Patient": {
"id": "Patient/35/_history/1",
"name": [
{
"given": [
"Andres Ernesto"
],
"family": "Kury Rivas"
}
],
"birthDate": "1996-07-09",
"gender": "male"
}
}
}
Como diriam alguns amigos meus, "lindo". Diferente da consulta GET, esta levou 98 milissegundos, ou seja, 70% (3.38x mais rápida) mais eficiente que a consulta GET.
Bem, avancei para algo um pouco mais complexo, então busquei uma forma de fazer uma consulta para obter as informações do paciente e suas observações. Quando falamos de observações no FHIR, estamos nos referindo a:
- Sinais vitais
- Dados laboratoriais
- Resultados de exames de imagem
- Achados clínicos
- Medições de dispositivos
- Antecedentes sociais
Entre outros dados. Assim, a consulta a ambos os recursos em um "único ato" ficou da seguinte forma:
GET http://r4.openfhir.org:8080/fhir/Patient/224/$everything?_type=Observation&_count=50
Essa consulta retornou como resposta o seguinte recurso:
{
"resourceType": "Bundle",
"id": "88a85d24-bfd4-4a54-a0fd-af072680fd09",
"meta": {
"lastUpdated": "2025-11-05T05:09:13.478+00:00"
},
"type": "searchset",
"total": 5,
"link": [
{
"relation": "self",
"url": "http://r4.openfhir.org:8080/fhir/Patient/224/$everything?_count=50&_type=Observation"
}
],
"entry": [
{
"fullUrl": "http://r4.openfhir.org:8080/fhir/Patient/224",
"resource": {
"resourceType": "Patient",
"id": "224",
"meta": {
"versionId": "1",
"lastUpdated": "2025-09-12T15:30:12.065+00:00"
},
"identifier": [
{
"system": "www.mypatientidentifier.com/ids",
"value": "andres.kury"
}
],
"name": [
{
"family": "kury",
"given": [
"andres"
]
}
]
},
"search": {
"mode": "match"
}
},
{
"fullUrl": "http://r4.openfhir.org:8080/fhir/Observation/219",
"resource": {
"resourceType": "Observation",
"id": "219",
"meta": {
"versionId": "1",
"lastUpdated": "2025-09-12T15:30:12.065+00:00"
},
"status": "final",
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8480-6",
"display": "Systolic blood pressure"
}
]
},
"subject": {
"reference": "Patient/224"
},
"effectiveDateTime": "2025-09-09T10:30:00-06:00",
"valueQuantity": {
"value": 120,
"unit": "mmHg"
}
},
"search": {
"mode": "match"
}
},
{
"fullUrl": "http://r4.openfhir.org:8080/fhir/Observation/220",
"resource": {
"resourceType": "Observation",
"id": "220",
"meta": {
"versionId": "1",
"lastUpdated": "2025-09-12T15:30:12.065+00:00"
},
"status": "final",
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8462-4",
"display": "Diastolic blood pressure"
}
]
},
"subject": {
"reference": "Patient/224"
},
"effectiveDateTime": "2025-09-09T10:30:00-06:00",
"valueQuantity": {
"value": 80,
"unit": "mmHg"
}
},
"search": {
"mode": "match"
}
},
{
"fullUrl": "http://r4.openfhir.org:8080/fhir/Observation/221",
"resource": {
"resourceType": "Observation",
"id": "221",
"meta": {
"versionId": "1",
"lastUpdated": "2025-09-12T15:30:12.065+00:00"
},
"status": "final",
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8867-4",
"display": "Heart rate"
}
]
},
"subject": {
"reference": "Patient/224"
},
"effectiveDateTime": "2025-09-09T10:30:00-06:00",
"valueQuantity": {
"value": 72,
"unit": "/min"
}
},
"search": {
"mode": "match"
}
},
{
"fullUrl": "http://r4.openfhir.org:8080/fhir/Observation/222",
"resource": {
"resourceType": "Observation",
"id": "222",
"meta": {
"versionId": "1",
"lastUpdated": "2025-09-12T15:30:12.065+00:00"
},
"status": "final",
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "9279-1",
"display": "Respiratory rate"
}
]
},
"subject": {
"reference": "Patient/224"
},
"effectiveDateTime": "2025-09-09T10:30:00-06:00",
"valueQuantity": {
"value": 16,
"unit": "/min"
}
},
"search": {
"mode": "match"
}
}
]
}
A execução levou 416 milissegundos. Fiz então o mesmo com GraphQL para ver como seria — lembrando que o GraphQL permite pedir de forma simples quais dados desejo obter, então a consulta ficou assim:
query {
patient: Patient(id: "224") { ...Pat }
observations: ObservationList(subject: "Patient/224", _count: 50) { ...Obs }
}
fragment Pat on Patient {
id birthDate name { given family }
}
fragment Obs on Observation {
id status effectiveDateTime
code { text coding { system code display } }
valueQuantity { value unit }
valueCodeableConcept { text coding { system code display } }
valueString
}
A resposta do servidor foi de 111 milissegundos. A consistência de velocidade e eficiência é exatamente a mesma: 73% mais eficiente (3.75x mais rápida). Nesse ponto eu já estava convencido de que mudaria a forma de consumir recursos FHIR para GraphQL puro.
Mas bem, até aqui tudo muito bem — e se pedirmos algo um pouco mais "pesado", como uma representação do resumo do paciente? Vamos ver como se sai. A consulta "geral" fica assim:
GET https://hapi.fhir.org/baseR4/Patient/7011747/$summary
A resposta do servidor foi bem grande, mas levou 1,37 segundos — já uma consulta mais complexa, cujo JSON retornado continha 2.619 linhas. Agora vamos ao GraphQL. Observação: o FHIR não especifica como obter uma operation $summary, então vamos emulá-la da seguinte forma:
query SummaryQuery {
patient: Patient(id: "7011747") {
id
name { given family }
gender
birthDate
address { city country }
telecom { system value }
}
problems: ConditionList(patient: "7011747", category: "problem-list-item", _count: 50) {
id
clinicalStatus { coding { ...CodingFields } }
verificationStatus { coding { ...CodingFields } }
code { ...Concept }
onsetDateTime
}
allergies: AllergyIntoleranceList(patient: "7011747", clinical_status: "active", _count: 50) {
id
code { ...Concept }
criticality
reaction { manifestation { ...Concept } severity }
}
medications: MedicationStatementList(patient: "7011747", _count: 50) {
id
status
medicationCodeableConcept { ...Concept }
effectiveDateTime
}
immunizations: ImmunizationList(patient: "7011747", _count: 50) {
id
status
vaccineCode { ...Concept }
occurrenceDateTime
}
# Labs: usa category "laboratory"
labs: ObservationList(patient: "7011747", category: "laboratory", _count: 50) {
id
status
code { ...Concept }
effectiveDateTime
valueQuantity { value unit }
valueCodeableConcept { ...Concept }
valueString
}
# Signos vitales: evita _profile; usa category "vital-signs"
vitals: ObservationList(patient: "7011747", category: "vital-signs", _count: 50) {
id
status
code { ...Concept }
effectiveDateTime
valueQuantity { value unit }
}
procedures: ProcedureList(patient: "7011747", _count: 50) {
id
status
code { ...Concept }
performedDateTime
}
}
fragment CodingFields on Coding {
system
code
display
}
fragment Concept on CodeableConcept {
text
coding { ...CodingFields }
}
O servidor retornou a resposta em 269 milissegundos; nesse caso, a eficiência foi de 80% (5.09x mais rápido). O JSON de resposta continha apenas 321 linhas.
Conclusões
- O GraphQL no FHIR realmente traz eficiência real: nos testes, as consultas com GraphQL foram sistematicamente mais rápidas e com respostas menores que suas equivalentes em REST.
- Menos "ruído", mais controle: o GraphQL permite pedir apenas os campos necessários, reduzir o over/under-fetching e navegar por referências em uma única consulta (ex.:
Patient+Observation), o que simplifica o consumo a partir do frontend e dos serviços intermediários. - Estado da especificação: a operação
$graphqlestá disponível desde o FHIR R4 e segue como Trial Use.
O GraphQL não substitui o REST no FHIR, mas é um excelente acelerador para experiências clínicas que exigem precisão nos campos e agregação de múltiplos recursos com baixa latência. Adotá-lo como uma camada opcional traz ganhos de desempenho e ergonomia para o cliente sem comprometer a interoperabilidade de base.


