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:

  1. O servidor que usamos no Curso de HL7 FHIR ministrado pela Secretaría General del Sistema de la Integración Centroamericana (SG-SICA)
  2. 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 $graphql está 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.

Fonte