# Fetch patient data with a FHIR API: Patient, labs, conditions, and medications.

A practical TypeScript pattern for reading common patient resources without assuming that every FHIR server behaves identically.

[Production-grade access is free with an account.](https://finchnode.com/signup) The $0/month plan includes 100 connected patient-months, 100,000 production API calls per month, one production application, and an unlimited synthetic sandbox.

- Author: [FinchNode Engineering](https://finchnode.com/authors/finchnode-engineering)

- Published: 2026-08-25

- Last reviewed: 2026-08-25

- Canonical URL: https://finchnode.com/blog/fhir-api-tutorial-patient-data

- Evidence boundary: Examples follow FHIR R4 REST and Bundle semantics and use fictional URLs and patient identifiers.

## Bottom line

Read the Patient resource first, then issue patient-scoped searches for the clinical resources your app is authorized to access. Follow the server’s Bundle `next` links verbatim, retain provenance, and distinguish an empty search from a failed search.

## Key takeaways

- A successful FHIR search returns a `searchset` Bundle, including when it contains zero matching resources.
- Follow the server-provided `next` URL rather than constructing page numbers yourself.
- Do not assume MedicationRequest alone represents every medication list returned by every source.

## Start with an explicit request plan

Common patient data categories and representative FHIR R4 resources

| Product category | FHIR resources to evaluate | Important fields |
| --- | --- | --- |
| Demographics | Patient | name, birthDate, gender, address, telecom |
| Labs and vitals | Observation, DiagnosticReport | status, category, code, value, effective date, reference range |
| Conditions | Condition | clinicalStatus, verificationStatus, code, onset |
| Medications | MedicationRequest, MedicationStatement | status, intent, medication, authoredOn |

## Follow FHIR Bundle pagination safely

```typescript
type FhirResource = { resourceType: string; id?: string };
type Bundle = {
  resourceType: 'Bundle';
  type: 'searchset';
  entry?: Array<{ resource?: FhirResource }>;
  link?: Array<{ relation: string; url: string }>;
};

export async function readAll(url: string, token: string, maxPages = 20) {
  const resources: FhirResource[] = [];
  let next: string | undefined = url;

  for (let page = 0; next && page < maxPages; page += 1) {
    const response = await fetch(next, {
      headers: { accept: 'application/fhir+json', authorization: `Bearer ${token}` },
    });
    if (!response.ok) throw await fhirError(response);
    const bundle = (await response.json()) as Bundle;
    if (bundle.resourceType !== 'Bundle') throw new Error('unexpected_fhir_response');
    resources.push(...(bundle.entry ?? []).flatMap((item) => item.resource ? [item.resource] : []));
    next = bundle.link?.find((link) => link.relation === 'next')?.url;
  }

  return resources;
}
```

## Representative patient-scoped searches

> Search support is advertised by a server’s CapabilityStatement and implementation guide. Treat these URLs as patterns, not a promise that every source accepts every parameter.

```http
GET {fhirBase}/Patient/{patientId}
GET {fhirBase}/Observation?patient={patientId}&category=laboratory&_count=100
GET {fhirBase}/Condition?patient={patientId}&_count=100
GET {fhirBase}/MedicationRequest?patient={patientId}&_count=100

Accept: application/fhir+json
Authorization: Bearer {accessToken}
```

## Normalize after preserving the source

- Store the FHIR base, resource type, logical ID, business identifiers, and `meta.lastUpdated`.
- Keep coding systems alongside display text; do not collapse different code systems into an unlabeled string.
- Represent missing values as unavailable instead of inventing defaults.
- Deduplicate only with source-aware identifiers and domain-specific rules.
- Keep the raw source resource or an integrity-protected reference when your policy permits it.

### Primary sources

- [FHIR R4 RESTful API](https://hl7.org/fhir/R4/http.html)
- [FHIR R4 resource identity](https://hl7.org/fhir/R4/resource.html)

## Frequently asked questions

### What does an empty FHIR search return?

A successful search normally returns a Bundle of type `searchset` with zero entries. That is different from a failed search, which should return an error status and typically an OperationOutcome.

### How do I paginate through FHIR results?

Follow the Bundle link whose relation is `next`. Do not infer or rewrite the continuation URL because servers can use opaque paging state.

### Which FHIR resource contains lab results?

Individual laboratory results are commonly represented as Observation resources, often with related DiagnosticReport resources. The exact profiles and search support depend on the server.

### Can I fetch every patient record with one request?

Some servers support Patient `$everything`, but its content, parameters, size, and paging behavior vary. Resource-specific searches are often easier to operate and troubleshoot.