Lesson 37 of 51 · The RESTful API
CRUD Interactions and the FHIR HTTP 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:
- read —
GET [base]/[type]/[id]fetches the current state of one resource. - vread —
GET [base]/[type]/[id]/_history/[vid]fetches one specific version of a resource, identified by its version id. - create —
POST [base]/[type]submits a new resource; the server assigns the logical id. - update —
PUT [base]/[type]/[id]creates or replaces the resource at a client-known id. - delete —
DELETE [base]/[type]/[id]removes a resource. - search —
GET [base]/[type]?param=valuereturns a searchsetBundleof 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
- HL7 FHIR Release 4 (R4), v4.0.1. HL7 International. 2019. verified Cited at: http.html.