> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ferrum.thalamiq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# CRUD

> How Ferrum handles create, read, update, patch, and delete operations.

## Implementation Features

**Spec**: [FHIR RESTful API](https://hl7.org/fhir/R4/http.html)

| Feature                | Support      | Notes                                |
| ---------------------- | ------------ | ------------------------------------ |
| **Create** (POST)      | Full         | Server-assigned IDs                  |
| **Read** (GET)         | Full         | With ETag support                    |
| **Update** (PUT)       | Full         | Update-as-create configurable        |
| **Patch** (PATCH)      | JSON Patch   | RFC 6902 format                      |
| **Delete**             | Configurable | Soft delete (default) or hard delete |
| **Conditional Create** | Full         | Via `If-None-Exist`                  |
| **Conditional Update** | Full         | Via query parameters                 |
| **Conditional Delete** | Full         | Single match required                |
| **Versioning**         | Full         | Sequential integer versionIds        |

## Conditional References (Search URIs)

Ferrum supports **conditional references** inside request resources by allowing a search URI in
`Reference.reference` (FHIR “search URIs”):

```json theme={null}
{
  "subject": { "reference": "Patient?identifier=http://example.org/fhir/mrn|12345" }
}
```

**Behavior**:

* **1 match**: Ferrum 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](https://hl7.org/fhir/R4/http.html#create),
[conditional create](https://hl7.org/fhir/R4/http.html#ccreate), [update-as-create](https://hl7.org/fhir/R4/http.html#upsert)

### Server-Assigned IDs

When creating with `POST`, Ferrum generates UUIDs as resource IDs:

```bash theme={null}
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).

```yaml theme={null}
fhir:
  allow_update_create: true # Allows PUT to create if resource doesn't exist
```

With this enabled:

```bash theme={null}
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"] }]
}'
```

<Warning>
  **Security Consideration**: Enabling `allow_update_create` allows clients to
  choose IDs. Ensure proper authorization to prevent ID conflicts or predictable
  ID attacks.
</Warning>

### Basic Checks on Create

By default, Ferrum 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

<Note>
  **FHIR Validation**: Full FHIR validation (cardinality, data types, profiles,
  etc.) is not yet implemented but will be in the future.
</Note>

### Conditional Create

Ferrum's conditional create follows FHIR spec:

```bash theme={null}
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`

<Tip>
  **Performance**: Conditional creates use indexed search, so ensure the search
  parameter (e.g., `identifier`) is indexed for fast lookups.
</Tip>

## Read Behavior

**Spec**: [read](https://hl7.org/fhir/R4/http.html#read),
[Concurrency Management (ETag/If-Match)](https://hl7.org/fhir/R4/http.html#versionaware),
[Support for HEAD](https://hl7.org/fhir/R4/http.html#head)

### ETag Support

Every read returns a weak [ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag) header for [optimistic locking](https://en.wikipedia.org/wiki/Optimistic_concurrency_control):

```bash theme={null}
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:

```bash theme={null}
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)

```json theme={null}
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "deleted",
      "diagnostics": "Resource Patient/123 was deleted"
    }
  ]
}
```

Access deleted resources via history (soft delete only):

```bash theme={null}
curl http://localhost:8080/fhir/Patient/123/_history/5
```

### HEAD Requests

Check resource existence without fetching body:

```bash theme={null}
curl -I http://localhost:8080/fhir/Patient/123

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

## Update Behavior

**Spec**: [update](https://hl7.org/fhir/R4/http.html#update),
[update-as-create](https://hl7.org/fhir/R4/http.html#upsert), [conditional update](https://hl7.org/fhir/R4/http.html#cond-update),
[Concurrency Management (If-Match)](https://hl7.org/fhir/R4/http.html#versionaware)

### Version Tracking

Ferrum uses **sequential integer versions** starting at 1:

```jsonc theme={null}
{
  "resourceType": "Patient",
  "id": "123",
  "meta": {
    "versionId": "1", // First version
    "lastUpdated": "2026-01-12T10:00:00Z"
  }
}
```

Each update increments the version:

```bash theme={null}
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:

```bash theme={null}
# 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`:

* Ferrum accepts the update (no concurrency protection)
* Last write wins (can lose concurrent changes)

With `If-Match`:

* Ferrum rejects if version doesn't match → `412 Precondition Failed`
* Client must refetch, merge changes, and retry

### Conditional Update

Update by search criteria:

```bash theme={null}
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`

<Warning>
  **Conditional Update + Multiple Matches**: Unlike some servers, Ferrum never
  updates multiple resources in a single request. This prevents accidental mass
  updates.
</Warning>

## Patch Behavior

**Spec**: [patch](https://hl7.org/fhir/R4/http.html#patch),
[JSON Patch (RFC 6902)](https://www.rfc-editor.org/rfc/rfc6902)

### JSON Patch Support

Ferrum supports JSON Patch (RFC 6902) format:

```bash theme={null}
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": "jane@example.com" } }
]'
```

**Supported operations**: `add`, `remove`, `replace`, `move`, `copy`, `test`

### Narrative Handling

Ferrum automatically **removes `text` (narrative)** after applying patches:

```jsonc theme={null}
// 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"] }]
}
```

<Note>
  **Why?** Narrative is auto-generated HTML that describes the resource. After a
  patch, the narrative no longer matches the data, so Ferrum removes it to prevent
  confusion.
</Note>

### Patch Result Checks

After applying patch operations, Ferrum performs basic structural checks:

* Patched resource must remain a valid JSON object
* Resource type and ID are preserved (cannot be changed via patch)

<Note>
  **FHIR Validation**: Full FHIR validation of the patched result is not yet
  implemented. The patched resource is stored as-is after basic structural
  checks.
</Note>

### 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

```bash theme={null}
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](https://hl7.org/fhir/R4/http.html#delete),
[conditional delete](https://hl7.org/fhir/R4/http.html#3.1.0.7.1)

### Soft Delete (Default)

Ferrum 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`)

<Warning>
  **Irreversible Operation**: Hard delete permanently removes all data and
  history. This cannot be undone. Use with caution, especially in production
  environments.
</Warning>

### Delete Response

```bash theme={null}
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:

```bash theme={null}
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:

```bash theme={null}
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

```bash theme={null}
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](https://hl7.org/fhir/R4/http.html#transaction)

### Indexing

Ferrum 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](/server/batch-and-transactions) instead of individual creates:

```bash theme={null}
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](https://hl7.org/fhir/R4/http.html#Status-Codes),
[OperationOutcome](https://hl7.org/fhir/R4/operationoutcome.html)

All errors return `OperationOutcome` with details:

| HTTP Status               | Meaning                      | Example                     |
| ------------------------- | ---------------------------- | --------------------------- |
| `400 Bad Request`         | Invalid resource             | Invalid structure or format |
| `404 Not Found`           | Resource doesn't exist       | Wrong ID                    |
| `409 Conflict`            | Version conflict             | Conflict during operation   |
| `410 Gone`                | Resource deleted             | Reading deleted resource    |
| `412 Precondition Failed` | Conditional operation failed | Multiple matches found      |

### Error Responses

Ferrum returns `OperationOutcome` resources for errors with diagnostic information:

```bash theme={null}
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" }
}'
```

```json theme={null}
{
  "resourceType": "OperationOutcome",
  "issue": [
    {
      "severity": "error",
      "code": "invalid",
      "diagnostics": "Resource type mismatch: expected Patient, got Observation"
    }
  ]
}
```

## Configuration

**Spec (Capability Declaration)**: [CapabilityStatement](https://hl7.org/fhir/R4/capabilitystatement.html)

Key configuration options for CRUD operations:

```yaml theme={null}
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](/getting-started/configuration) for complete options.

## Testing Your Implementation

**Spec (Capabilities)**: [capabilities (`GET [base]/metadata`)](https://hl7.org/fhir/R4/http.html#capabilities)

Use the interactive API playground to test CRUD operations:

<CardGroup cols={2}>
  <Card title="Create" href="/api-reference/endpoint/create">
    Try creating resources
  </Card>

  <Card title="Read" href="/api-reference/endpoint/read">
    Test read operations
  </Card>

  <Card title="Update" href="/api-reference/endpoint/update">
    Practice updates
  </Card>

  <Card title="Delete" href="/api-reference/endpoint/delete">
    Test deletion
  </Card>
</CardGroup>

## Next Steps

**Spec**: [search](https://hl7.org/fhir/R4/http.html#search),
[batch/transaction](https://hl7.org/fhir/R4/http.html#transaction), [history](https://hl7.org/fhir/R4/http.html#history)

<CardGroup cols={2}>
  <Card title="Search Operations" href="/server/search">
    Query resources efficiently
  </Card>

  <Card title="Batch & Transaction" href="/server/batch-and-transactions">
    Perform multiple operations atomically
  </Card>

  <Card title="Versioning & History" href="/server/versioning-history">
    Access resource history
  </Card>
</CardGroup>

## Related Documentation

* Background: [Learn FHIR: CRUD](/learn-fhir/crud)
* Endpoints: [Create](/api-reference/endpoint/create), [Read](/api-reference/endpoint/read), [Update](/api-reference/endpoint/update), [Patch](/api-reference/endpoint/patch), [Delete](/api-reference/endpoint/delete)
* Multi-request writes: [Learn FHIR: Batch & Transaction](/learn-fhir/batch-and-transaction)
