Lesson 0009 · FHIR · Modul REST · ±25 menit

Chaining, _include, _revinclude; paging dan Bundle searchset

Filter lewat referensi, sertakan resource yang dirujuk, lalu ikuti halaman hasilnya

Target lesson ini: Anda bisa menjawab "Bagaimana mengambil semua Observation milik satu pasien beserta Patient-nya dalam satu request? Bagaimana kalau hasilnya ribuan?" tanpa catatan: chaining vs _include/_revinclude, entry.search.mode, dan paging lewat link next.

Sebelumnya, di lesson 0008, Anda menyusun query search: tipe parameter, modifier, prefix, AND/OR. Lesson ini menjawab dua pertanyaan berikutnya: bagaimana membawa resource yang dirujuk dalam request yang sama, dan bagaimana membaca hasil yang dibagi per halaman.

1. Chaining, _include, dan _revinclude

Dua hal yang sering tertukar: chaining memfilter lewat referensi, _include menambah resource yang dirujuk ke hasil.

Contoh resmiArtinya
ChainingDiagnosticReport?subject:Patient.name=peter"all the lab reports that have a subject which is a patient, whose name includes "peter"". Hasilnya tetap DiagnosticReport saja.
_includeMedicationRequest?_include=MedicationRequest:patientsertakan "any patient that the medication prescriptions in the result set refer to" (arah maju: hasil → yang dirujuk)
_revincludeMedicationRequest?_revinclude=Provenance:targetsertakan "all the provenance resources that refer to them" (arah balik: yang merujuk → hasil)

Semua: [2]. Nilai _include/_revinclude berbentuk [tipe sumber]:[search parameter] (opsional :[tipe target]); include berantai hanya terjadi dengan modifier :iterate. Chaining terbalik (_has) ada, tapi dukungannya "limited".

Jawaban untuk pertanyaan target, dari dua arah:

// Dari Observation: hasil = Observation, Patient ikut sebagai include
GET [base]/Observation?patient=123&code=http://loinc.org|29463-7&_include=Observation:patient

// Dari Patient: hasil = Patient 123, Observation yang merujuknya ikut sebagai include
GET [base]/Patient?_id=123&_revinclude=Observation:patient

Padanan JS: _include mirip include di Prisma (prisma.observation.findMany({ where: …, include: { patient: true } })), tapi hasilnya tidak bersarang: Patient muncul sebagai entry terpisah di Bundle (bagian 2), dan bisa muncul lagi di halaman berikutnya bila dirujuk Observation di halaman itu. [2] Kode LOINC 29463-7 (Body Weight) diambil dari contoh Observation resmi R4. [4]

Latihan 1 — Filter atau sertakan?

2. Paging dan Bundle searchset

Hasil search selalu Bundle dengan type: "searchset". [1] Bentuk ringkasnya:

{
  "resourceType": "Bundle",
  "type": "searchset",
  "total": 5000,                                   // jumlah match di SEMUA halaman (include tidak dihitung)
  "link": [
    { "relation": "self", "url": "https://fhir.example.org/Observation?patient=123&_include=Observation:patient&_count=50" },
    { "relation": "next", "url": "https://fhir.example.org/Observation?_getpages=a1b2c3&_offset=50" }
  ],
  "entry": [
    { "fullUrl": "https://fhir.example.org/Observation/obs-1",
      "resource": { "resourceType": "Observation", "id": "obs-1", "...": "..." },
      "search": { "mode": "match" } },
    { "fullUrl": "https://fhir.example.org/Patient/123",
      "resource": { "resourceType": "Patient", "id": "123", "...": "..." },
      "search": { "mode": "include" } }
  ]
}
// Node 18+ / browser: ikuti link "next" sampai habis, tanpa menyusun URL halaman sendiri
async function* searchAll(url: string): AsyncGenerator<any> {
  let next: string | undefined = url;
  while (next) {
    const res = await fetch(next, { headers: { Accept: "application/fhir+json" } });
    if (!res.ok) throw new Error(JSON.stringify(await res.json()));   // body = OperationOutcome (lesson 0011)
    const bundle = await res.json();
    for (const entry of bundle.entry ?? []) yield entry;              // cek entry.search.mode
    next = bundle.link?.find((l: any) => l.relation === "next")?.url;
  }
}

Untuk jutaan resource, paging search bukan alat yang tepat; itu tugas Bulk Data $export di lesson 0013.

Latihan 2 — Membaca Bundle hasil search

Latihan 3 — Ucapkan dengan suara keras (±5 menit)

Pasang timer dan jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.

"Bagaimana Anda mengambil semua Observation berat badan milik satu pasien, beserta Patient-nya, dalam satu request? Bagaimana kalau hasilnya ribuan?" (90 detik)
Bandingkan dengan jawaban contoh dan checklist-nya

"Saya search Observation: GET [base]/Observation?patient=123&code=http://loinc.org|29463-7&_include=Observation:patient&_count=50. code itu token, jadi saya tulis system|code supaya tidak tertukar dengan sistem kode lain; kalau perlu rentang waktu, tambah date=ge2024-01-01&date=le2024-12-31, parameter yang diulang berarti AND. _include membuat server ikut mengirim Patient yang dirujuk, dan di Bundle searchset saya bedakan lewat entry.search.mode: match untuk Observation, include untuk Patient. Alternatifnya dari sisi Patient: Patient?_id=123&_revinclude=Observation:patient. Kalau ribuan, server mem-page hasilnya; saya ikuti Bundle.link dengan relation next sampai habis, tidak menyusun URL halaman sendiri karena link itu opaque. Jumlah total ada di Bundle.total, yang hanya menghitung match. Di Prisma, padanannya findMany dengan include plus cursor pagination."

Checklist: apakah Anda menyebut…

  • search Observation dengan patient=123 (atau subject=Patient/123)?
  • code sebagai token http://loinc.org|29463-7 (system|code)?
  • _include=Observation:patient, atau _revinclude dari sisi Patient?
  • hasil Bundle searchset; entry.search.mode membedakan match dan include?
  • paging: _count dan mengikuti link next yang opaque, bukan menyusun URL sendiri?
  • filter tanggal dengan prefix ge/le, parameter diulang = AND, koma = OR?

Lulus kalau minimal 4 dari 6.

Ringkasan untuk dicetak: Reference: REST, search, Bundle · Istilah: GLOSSARY.md (bagian "REST API")

Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "bagaimana _include=...:iterate bekerja?"

Sumber (semua FHIR R4 4.0.1, diakses 2026-10-26): [1] RESTful API · [2] Search · [3] Bundle · [4] Observation example (Body Weight)