Lesson 37 of 51 · The RESTful API

CRUD Interactions and the FHIR HTTP API

FHIR RESTful API

CRUD over HTTP

You already know that a FHIR resource is typed and individually addressable at [base]/[type]/[id]. The next question is how a client actually does something with that address. FHIR answers it with a RESTful API: the four classic data operations — create, read, update, delete (CRUD) — are mapped onto standard HTTP verbs acting on resource URLs 1. Because the mapping is regular, once you learn it for one resource type it works for every other type the server exposes.

The core instance-level and type-level interactions are 1:

  • readGET [base]/[type]/[id] fetches the current state of one resource.
  • vreadGET [base]/[type]/[id]/_history/[vid] fetches one specific version of a resource, identified by its version id.
  • createPOST [base]/[type] submits a new resource; the server assigns the logical id.
  • updatePUT [base]/[type]/[id] creates or replaces the resource at a client-known id.
  • deleteDELETE [base]/[type]/[id] removes a resource.
  • searchGET [base]/[type]?param=value returns a searchset Bundle of matching resources; search is covered in detail in the next lesson.

Notice the division of labor over the id. With create the client does not know the id in advance, so it posts to the type endpoint and lets the server mint one. With update the client already knows the id and addresses the instance directly, which is why PUT can also create a resource at that id if the server allows it. That “create-or-replace at a known id” behavior is what distinguishes PUT from POST here.

Example requests

The following requests use a base URL of https://example.org/fhir and a Patient resource whose logical id is pat-1:

# read: fetch the current Patient
GET https://example.org/fhir/Patient/pat-1

# vread: fetch a specific historical version
GET https://example.org/fhir/Patient/pat-1/_history/2

# create: server assigns the id; body carries the new resource
POST https://example.org/fhir/Patient
Content-Type: application/fhir+json
{ "resourceType": "Patient", "name": [ { "family": "Lee" } ] }

# update: create-or-replace at a known id; body carries the full resource
PUT https://example.org/fhir/Patient/pat-1
Content-Type: application/fhir+json
{ "resourceType": "Patient", "id": "pat-1", "name": [ { "family": "Lee" } ] }

# delete: remove the resource
DELETE https://example.org/fhir/Patient/pat-1

# search: returns a searchset Bundle of matches
GET https://example.org/fhir/Patient?family=Lee

The verb plus the shape of the URL fully determines the interaction: a GET on a type-and-id path is a read, the same path with /_history/[vid] appended is a vread, a POST to the bare type path is a create, and so on 1.

Versions and safe concurrent change

Every resource carries a version. The current version’s id appears in the resource’s meta.versionId, and past versions remain retrievable by vread 1. Versioning is not just bookkeeping — it is what lets the API support optimistic concurrency control, so two clients editing the same record do not silently overwrite each other.

The mechanism uses ordinary HTTP. A read response carries the current version as an HTTP ETag. When a client later sends an update (or delete), it can echo that value in an If-Match header. The server applies the change only if the resource is still at that version; if someone else has changed it in the meantime, the server rejects the request rather than clobbering the newer data 1. The same conditional style underpins conditional interactions — for example, “create this resource only if no matching one already exists” — letting a client express intent without first doing a separate lookup.

Status codes and why “just HTTP” matters

Responses report their outcome with standard HTTP status codes 1. A successful read returns 200 OK; a successful create returns 201 Created; a request for a resource that does not exist returns 404 Not Found; a failed concurrency check returns a conflict status. Errors are typically accompanied by an OperationOutcome resource describing what went wrong, so the failure is itself expressed in FHIR.

The deeper point is that none of this is exotic. A FHIR server is, at bottom, HTTP plus JSON (or XML): resources are addressed by URL, fetched with GET, versioned with ETag, and reported on with familiar status codes. That means the entire ecosystem of ordinary web tooling applies without modification — HTTP libraries and clients, proxies and HTTP caches keyed on URLs and ETags, and standard web security such as TLS and OAuth-based authorization. Developers do not have to learn a bespoke transport or build special-purpose clients; they reuse skills and infrastructure they already have. This deliberate reuse of the web is a major reason FHIR has been adopted so quickly compared with earlier, more specialized healthcare interchange technologies 1.

References

  1. HL7 FHIR Release 4 (R4), v4.0.1. HL7 International. 2019. verified Cited at: http.html.