Overview
FHIR resources frequently reference each other. For example, an Observation may reference a Patient via Observation.subject. Referential integrity controls whether the server validates that these references point to resources that actually exist.
Zunder supports two modes:
| Mode | Behavior |
|---|
| lenient | No reference checking. Dangling references are allowed. (Default) |
| strict | Rejects writes with broken references. Blocks deletes of referenced resources. |
Configuration
Config file
fhir:
referential_integrity:
mode: lenient # "lenient" (default) or "strict"
Environment variable
FHIR__REFERENTIAL_INTEGRITY__MODE=strict
Behavior
On create and update (strict mode)
When a resource is created or updated, Zunder extracts all references from the resource JSON and validates that each referenced resource exists in the database.
What is checked:
- Relative references (e.g.,
Patient/123, Observation/abc)
What is NOT checked:
- Fragment references to contained resources (e.g.,
#p1)
- Absolute URLs to external servers (e.g.,
http://external.example.com/Patient/1)
- Canonical URLs (e.g.,
http://hl7.org/fhir/StructureDefinition/Patient)
- Self-references (a resource referencing its own
resourceType/id)
If any referenced resource does not exist, the server returns 409 Conflict with an OperationOutcome listing the broken references:
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "business-rule",
"diagnostics": "Referential integrity violation: the following referenced resources do not exist: Patient/nonexistent-999"
}
]
}
On delete (strict mode)
Before deleting a resource, Zunder checks whether any other resource references it via the search_reference index. If references exist, the delete is blocked with 409 Conflict:
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "business-rule",
"diagnostics": "Cannot delete Patient/123: it is referenced by Observation/abc, Condition/def"
}
]
}
The delete check queries the search_reference index table, which is populated by the indexing pipeline. If indexing has not yet completed for a recently created resource, the reverse reference may not be detected.
In batch bundles
Each entry in a batch bundle is validated independently. If one entry has broken references in strict mode, only that entry fails; other entries proceed normally.
In transaction bundles
Transaction bundles are validated with awareness of resources created earlier in the same transaction. For example, if a transaction creates a Patient in one entry and an Observation referencing that Patient in a subsequent entry, the reference is considered valid even though the Patient does not yet exist in the database.
Delete entries within a transaction are also validated: you cannot delete a resource that is referenced by resources outside the transaction.
Lenient mode
In lenient mode (the default), no reference validation occurs. Resources can reference non-existent targets, and any resource can be deleted regardless of incoming references. This is the most permissive setting and matches the behavior of many FHIR servers.
Limitations
- Only relative references are validated. External URLs, canonical references, and contained references are not checked.
- No cascade delete. In strict mode, you must delete referencing resources before deleting the target. Cascade delete may be added in a future release.
- Delete checks depend on the search index. If indexing is delayed (e.g., background job queue), recently created references may not block a delete.
Next Steps