Skip to main content

Implementation Features

Spec: FHIR RESTful API
FeatureSupportNotes
Create (POST)βœ… FullServer-assigned IDs
Read (GET)βœ… FullWith ETag support
Update (PUT)βœ… FullUpdate-as-create configurable
Patch (PATCH)βœ… JSON PatchRFC 6902 format
Deleteβœ… ConfigurableSoft delete (default) or hard delete
Conditional Createβœ… FullVia If-None-Exist
Conditional Updateβœ… FullVia query parameters
Conditional Deleteβœ… FullSingle match required
Versioningβœ… FullSequential integer versionIds

Conditional References (Search URIs)

TLQ FHIR supports conditional references inside request resources by allowing a search URI in Reference.reference (FHIR β€œsearch URIs”):
{
  "subject": { "reference": "Patient?identifier=http://example.org/fhir/mrn|12345" }
}
Behavior:
  • 1 match: TLQ FHIR rewrites the reference to Patient/{id} and persists the rewritten form.
  • 0 matches or 2+ matches: request fails with 412 Precondition Failed and no write occurs.
  • Only filter search parameters are allowed (e.g. _count, _sort, _include, _revinclude, _elements, _summary are rejected).
This applies to POST, PUT, and PATCH (including conditional interactions), and also to bundle processing (batch / transaction).

Create Behavior

Spec: create, conditional create, update-as-create

Server-Assigned IDs

When creating with POST, TLQ FHIR generates UUIDs as resource IDs:
curl -X POST http://localhost:8080/fhir/Patient \
  -H "Content-Type: application/fhir+json" \
  -d '{
  "resourceType": "Patient",
  "name": [{ "family": "Doe", "given": ["Jane"] }]
}'

Client-Assigned IDs

The allow_update_create configuration option allows PUT to create resources if they don’t exist (enabled by default).
fhir:
  allow_update_create: true # Allows PUT to create if resource doesn't exist
With this enabled:
curl -X PUT http://localhost:8080/fhir/Patient/my-custom-id \
  -H "Content-Type: application/fhir+json" \
  -d '{
  "resourceType": "Patient",
  "id": "my-custom-id",
  "name": [{ "family": "Doe", "given": ["Jane"] }]
}'
Security Consideration: Enabling allow_update_create allows clients to choose IDs. Ensure proper authorization to prevent ID conflicts or predictable ID attacks.

Basic Checks on Create

By default, TLQ FHIR performs basic structural checks on creates:
  1. Resource Type: Validates that resourceType matches the endpoint
  2. Resource Type Name: Ensures the resource type is a known FHIR resource type
FHIR Validation: Full FHIR validation (cardinality, data types, profiles, etc.) is not yet implemented but will be in the future.

Conditional Create

TLQ FHIR’s conditional create follows FHIR spec:
curl -X POST "http://localhost:8080/fhir/Patient" \
  -H "Content-Type: application/fhir+json" \
  -H 'If-None-Exist: identifier=http://hospital.example/mrn|12345' \
  -d '{
  "resourceType": "Patient",
  "identifier": [{ "system": "http://hospital.example/mrn", "value": "12345" }],
  "name": [{ "family": "Doe", "given": ["Jane"] }]
}'
Behavior:
  • 0 matches: Creates new resource β†’ 201 Created
  • 1 match: Returns existing resource β†’ 200 OK with Location header
  • 2+ matches: Rejects as ambiguous β†’ 412 Precondition Failed
Performance: Conditional creates use indexed search, so ensure the search parameter (e.g., identifier) is indexed for fast lookups.

Read Behavior

Spec: read, Concurrency Management (ETag/If-Match), Support for HEAD

ETag Support

Every read returns a weak ETag header for optimistic locking:
curl -i http://localhost:8080/fhir/Patient/123

# Look for response headers like:
# ETag: W/"5"
# Last-Modified: Mon, 12 Jan 2026 10:30:00 GMT
Use If-Match on updates to prevent lost updates:
curl -X PUT http://localhost:8080/fhir/Patient/123 \
  -H "Content-Type: application/fhir+json" \
  -H 'If-Match: W/"5"' \
  -d '{
  "resourceType": "Patient",
  "id": "123",
  "name": [{ "family": "Doe", "given": ["Jane"] }]
}'

Deleted Resources

Reading a deleted resource behavior depends on delete mode: Soft Delete (default):
  • Returns 410 Gone for deleted resources
  • History remains accessible via GET /Patient/123/_history/5
Hard Delete (hard_delete: true):
  • Returns 404 Not Found (resource completely removed)
  • History is not accessible (all versions deleted)
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "deleted",
      "diagnostics": "Resource Patient/123 was deleted"
    }
  ]
}
Access deleted resources via history (soft delete only):
curl http://localhost:8080/fhir/Patient/123/_history/5

HEAD Requests

Check resource existence without fetching body:
curl -I http://localhost:8080/fhir/Patient/123

# 200 OK = exists
# 404 Not Found = doesn't exist
# 410 Gone = deleted

Update Behavior

Spec: update, update-as-create, conditional update, Concurrency Management (If-Match)

Version Tracking

TLQ FHIR uses sequential integer versions starting at 1:
{
  "resourceType": "Patient",
  "id": "123",
  "meta": {
    "versionId": "1", // First version
    "lastUpdated": "2026-01-12T10:00:00Z"
  }
}
Each update increments the version:
curl -X PUT http://localhost:8080/fhir/Patient/123 \
  -H "Content-Type: application/fhir+json" \
  -d '{
  "resourceType": "Patient",
  "id": "123",
  "name": [{ "family": "Doe", "given": ["Jane"] }]
}'

Optimistic Locking

Always use If-Match for safe updates:
# Good: Safe update
curl -X PUT http://localhost:8080/fhir/Patient/123 \
  -H "Content-Type: application/fhir+json" \
  -H 'If-Match: W/"5"' \
  -d '{
  "resourceType": "Patient",
  "id": "123",
  "name": [{ "family": "Doe", "given": ["Jane"] }]
}'
Without If-Match:
  • TLQ FHIR accepts the update (no concurrency protection)
  • Last write wins (can lose concurrent changes)
With If-Match:
  • TLQ FHIR rejects if version doesn’t match β†’ 412 Precondition Failed
  • Client must refetch, merge changes, and retry

Conditional Update

Update by search criteria:
curl -X PUT "http://localhost:8080/fhir/Patient?identifier=http://hospital.example/mrn|12345" \
  -H "Content-Type: application/fhir+json" \
  -d '{
  "resourceType": "Patient",
  "identifier": [{ "system": "http://hospital.example/mrn", "value": "12345" }],
  "name": [{ "family": "Doe", "given": ["Jane"] }]
}'
Behavior:
  • 0 matches: Creates new resource (if allow_update_create enabled) β†’ 201 Created
  • 1 match: Updates that resource β†’ 200 OK
  • 2+ matches: Rejects as ambiguous β†’ 412 Precondition Failed
Conditional Update + Multiple Matches: Unlike some servers, TLQ FHIR never updates multiple resources in a single request. This prevents accidental mass updates.

Patch Behavior

Spec: patch, JSON Patch (RFC 6902)

JSON Patch Support

TLQ FHIR supports JSON Patch (RFC 6902) format:
curl -X PATCH http://localhost:8080/fhir/Patient/123 \
  -H "Content-Type: application/json-patch+json" \
  -d '[
  { "op": "replace", "path": "/name/0/given/0", "value": "Jane" },
  { "op": "add", "path": "/telecom/-", "value": { "system": "email", "value": "[email protected]" } }
]'
Supported operations: add, remove, replace, move, copy, test

Narrative Handling

TLQ FHIR automatically removes text (narrative) after applying patches:
// Before patch
{
  "resourceType": "Patient",
  "text": { "status": "generated", "div": "<div>John Doe</div>" },
  "name": [{ "family": "Doe", "given": ["John"] }]
}

// After patch (changing name)
{
  "resourceType": "Patient",
  // text removed to prevent stale narrative
  "name": [{ "family": "Doe", "given": ["Jane"] }]
}
Why? Narrative is auto-generated HTML that describes the resource. After a patch, the narrative no longer matches the data, so TLQ FHIR removes it to prevent confusion.

Patch Result Checks

After applying patch operations, TLQ FHIR performs basic structural checks:
  • Patched resource must remain a valid JSON object
  • Resource type and ID are preserved (cannot be changed via patch)
FHIR Validation: Full FHIR validation of the patched result is not yet implemented. The patched resource is stored as-is after basic structural checks.

Atomic Patches

All operations in a patch are atomic:
  • Either all operations succeed, or none do
  • Failed operation rolls back the entire patch
  • Version number only increments if all operations succeed

Conditional Patch

curl -X PATCH "http://localhost:8080/fhir/Patient?identifier=http://hospital.example/mrn|12345" \
  -H "Content-Type: application/json-patch+json" \
  -d '[
  { "op": "replace", "path": "/name/0/given/0", "value": "Jane" }
]'
Same matching rules as conditional update (requires exactly 1 match).

Delete Behavior

Spec: delete, conditional delete

Soft Delete (Default)

TLQ FHIR uses soft delete by default (hard_delete: false):
  • Resource marked as deleted in database
  • History preserved and accessible
  • GET /Patient/123 β†’ 410 Gone
  • GET /Patient/123/_history/5 β†’ 200 OK (still accessible)
  • Creates a new version entry marking the resource as deleted

Hard Delete

When hard_delete: true is configured:
  • Resource physically removed from database
  • All history versions permanently deleted
  • GET /Patient/123 β†’ 404 Not Found (not 410 Gone)
  • History endpoints return 404 Not Found
  • No new version created (complete removal)
  • Enables deletion of history endpoints (DELETE /Patient/123/_history)
Irreversible Operation: Hard delete permanently removes all data and history. This cannot be undone. Use with caution, especially in production environments.

Delete Response

curl -i -X DELETE http://localhost:8080/fhir/Patient/123

# Success responses (both valid):
# HTTP/1.1 200 OK          # With OperationOutcome body
# HTTP/1.1 204 No Content  # No body
Configure response style with Prefer header:
curl -i -X DELETE http://localhost:8080/fhir/Patient/123 \
  -H "Prefer: return=OperationOutcome"

# Example response body:
# {
#   "resourceType": "OperationOutcome",
#   "issue": [{
#     "severity": "information",
#     "code": "informational",
#     "diagnostics": "Resource deleted successfully"
#   }]
# }

Idempotent Delete

Deleting an already-deleted resource succeeds:
curl -i -X DELETE http://localhost:8080/fhir/Patient/123  # First delete
curl -i -X DELETE http://localhost:8080/fhir/Patient/123  # Second delete (idempotent)

Conditional Delete

curl -i -X DELETE "http://localhost:8080/fhir/Patient?identifier=http://hospital.example/mrn|12345"
Behavior:
  • 0 matches: No-op, returns 204 No Content
  • 1 match: Deletes that resource β†’ 204 No Content
  • 2+ matches: Rejects as ambiguous β†’ 412 Precondition Failed

Performance Considerations

Spec (Batch/Transaction): batch/transaction

Indexing

TLQ FHIR automatically indexes:
  • Resource ID (primary key)
  • Version ID
  • Last updated timestamp
  • Common search parameters (identifier, name, etc.)
Conditional operations use these indexes for fast lookups.

Batch Creates

For bulk data loading, use Batch/Transaction instead of individual creates:
curl -X POST http://localhost:8080/fhir \
  -H "Content-Type: application/fhir+json" \
  -d '{
  "resourceType": "Bundle",
  "type": "transaction",
  "entry": [
    {
      "request": { "method": "POST", "url": "Patient" },
      "resource": { "resourceType": "Patient", "name": [{ "family": "Doe", "given": ["Jane"] }] }
    },
    {
      "request": { "method": "POST", "url": "Patient" },
      "resource": { "resourceType": "Patient", "name": [{ "family": "Doe", "given": ["John"] }] }
    }
  ]
}'
Benefits:
  • Single database transaction
  • Reduced network overhead
  • Better error handling

Error Handling

Spec: HTTP Status Codes, OperationOutcome All errors return OperationOutcome with details:
HTTP StatusMeaningExample
400 Bad RequestInvalid resourceInvalid structure or format
404 Not FoundResource doesn’t existWrong ID
409 ConflictVersion conflictConflict during operation
410 GoneResource deletedReading deleted resource
412 Precondition FailedConditional operation failedMultiple matches found

Error Responses

TLQ FHIR returns OperationOutcome resources for errors with diagnostic information:
curl -i -X POST http://localhost:8080/fhir/Patient \
  -H "Content-Type: application/fhir+json" \
  -d '{
  "resourceType": "Observation",
  "status": "final",
  "code": { "text": "Not a Patient" }
}'
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "invalid",
      "diagnostics": "Resource type mismatch: expected Patient, got Observation"
    }
  ]
}

Configuration

Spec (Capability Declaration): CapabilityStatement Key configuration options for CRUD operations:
fhir:
  # Create/Update
  allow_update_create: true # Allow PUT to create resources

  # Delete
  hard_delete: false # Use hard delete (true) or soft delete (false, default)

  # Versioning
  max_history_versions: 100 # Max versions to keep (0 = unlimited)

  # Performance
  max_resource_size: 5242880 # Max resource size (5MB default)
See Configuration Guide for complete options.

Testing Your Implementation

Spec (Capabilities): capabilities (GET [base]/metadata) Use the interactive API playground to test CRUD operations:

Next Steps

Spec: search, batch/transaction, history