Lightweight FHIR 6.0 server implementation in Rust.
- JSON-only API
- PostgreSQL-backed storage (
JSONB) - Multi-tenant context from JWT (
tenant/subclaim) - Mandatory bearer-token authentication with configurable JWT verification
- FHIR JSON Schema validation backed by the bundled
fhir.schema.json - FHIR
OperationOutcomeerror responses for auth and request validation failures - Endpoints:
GET /healthzGET /metadataPOST /fhir/:resource_typeGET /fhir/:resource_type/:idGET /fhir/:resource_type/:id/_historyPUT /fhir/:resource_type/:idDELETE /fhir/:resource_type/:idGET /fhir/:resource_typewith_countand_after_id
DATABASE_URL(required)DB_CONNECT_TIMEOUT_SECS(default:5)DB_ACQUIRE_TIMEOUT_SECS(default:5)DB_STATEMENT_TIMEOUT_MS(default:10000)BIND_ADDR(default:0.0.0.0:8080)FHIR_BASE_URL(default:http://localhost:8080/fhir)SEARCH_DEFAULT_COUNT(default:128)SEARCH_MAX_COUNT(default:2048)CORS_ALLOWED_ORIGINS(optional comma-separated origin allowlist; unset means no cross-origin access)SERVE_DOCS(default:false; set totrueto expose/docswith vendored Swagger UI assets)
The server supports two JWT modes selected via JWT_MODE:
Verify tokens against a single static key — either HMAC or an asymmetric PEM.
| Variable | Description |
|---|---|
JWT_ALGORITHM |
HS256 (default), HS384, HS512, RS256, RS384, RS512, ES256, ES384 |
JWT_SECRET |
Required for HMAC algorithms; minimum 32 chars |
JWT_PUBLIC_KEY_PEM or JWT_PUBLIC_KEY_PATH |
Required for RSA/ECDSA algorithms |
JWT_ISSUER |
Optional — validate the iss claim |
JWT_AUDIENCE |
Optional — validate the aud claim |
For production, prefer asymmetric verification (RS256/ES256) with a mounted public key.
Fetch signing keys from an OpenID Connect / OAuth2 JWKS endpoint. Keys are loaded at startup (server fails fast if unreachable) and refreshed in the background.
| Variable | Description |
|---|---|
JWT_JWKS_URI |
Required — URL to the provider's /.well-known/jwks.json |
JWT_JWKS_REFRESH_SECS |
Background refresh interval (default: 300) |
JWT_ISSUER |
Optional — validate the iss claim |
JWT_AUDIENCE |
Optional — validate the aud claim |
The server decodes each token's kid header to find the matching key in the JWKS set. Tokens without a kid are rejected.
export JWT_SECRET="$(openssl rand -hex 32)"
JWT_MODE=static \
JWT_ALGORITHM=HS256 \
SERVE_DOCS=true \
DATABASE_URL=postgres://postgres:postgres@localhost/postgres \
cargo runMost FHIR endpoints require an Authorization: Bearer <token> header. Only GET /healthz and GET /metadata are intentionally public.
Tenant handling is explicit:
- the server requires either a
tenantclaim or asubclaim - if both are present,
tenanttakes precedence oversub - there is no default tenant on the server side
Issuer and audience validation are opt-in:
- set
JWT_ISSUERto require a matchingissclaim - set
JWT_AUDIENCEto require a matchingaudclaim - if these variables are unset, those claims are not validated
For local testing with static HMAC mode, start the server as shown above and then generate a compatible JWT:
python3 scripts/generate_static_jwt.py \\
--tenant my-tenant \
--scope 'read write'The script reads JWT_SECRET from the environment by default. Use --secret only if you intentionally want to override it.
The script prints a JWT. Use it in requests like this:
TOKEN="paste-token-here"
curl -s http://localhost:8080/fhir/Patient \
-H "Authorization: Bearer $TOKEN"When SERVE_DOCS=true, the docs are available at http://localhost:8080/docs/.
- The OpenAPI document now advertises HTTP bearer auth for the protected
/fhir/...routes. - Swagger UI shows an
Authorizebutton where you can paste the JWT token. - The UI is configured to persist the authorization between page reloads in the same browser profile.
Paste only the raw JWT value into the Swagger UI auth dialog unless the dialog explicitly asks for the full header value.
Swagger UI cannot mint a token for you. You must obtain a valid JWT from the configured auth source:
JWT_MODE=static: issue a token signed by your configured HMAC secret or matching private key.JWT_MODE=jwks: obtain a token from the external OpenID Connect or OAuth2 provider behind the configured JWKS endpoint.
Whichever mode you use, the token must satisfy this server's authorization checks:
- include either
tenantorsub - include
scopewithread,write, or both depending on the operation - optionally include
resource_typesto restrict access to specific FHIR resource types
From the repository root, run the Python E2E harness against the native server, the Docker image, or both:
python3 scripts/e2e_examples.py --mode bothUseful variants:
python3 scripts/e2e_examples.py --mode native --native-db auto --dataset all-plus-smoke --workers 12
python3 scripts/e2e_examples.py --mode docker --dataset all-plus-smoke --workers 12What it does:
- Ensures the HL7
examples-json.zipdataset is present, downloading and extracting it automatically when needed. - In native mode, prefers a local PostgreSQL instance when available and falls back to disposable PostgreSQL via
docker compose -f docker-compose.e2e.yml. - Runs the server natively with
cargo run --releaseor as the built container. - Scans the whole
examples/directory, infers each example type from its top-levelresourceType, and uses parallel HTTP requests to keep the run time reasonable. - Treats schema-valid examples as accepted resources, schema-invalid official examples as expected
OperationOutcomerejections, and oversized payload transport failures as an explicit current limitation. - Uses a small CRUD smoke flow on a known-good subset when
--dataset smokeor--dataset all-plus-smokeis selected.
- Capability statement advertises create/read/history-instance/update/patch/delete/search-type interactions.
- Collection search uses cursor pagination ordered by resource id. Clients request the first page with
_countand follow the returnednextlink using_after_id. - HTTP response compression is enabled through
tower-httpcontent negotiation. Clients can request compressed FHIR responses with standardAccept-Encodingvalues such asgzip,br, ordeflate. - Create and update requests validate both the FHIR envelope (
resourceType,id) and the resource-specific JSON Schema definition. - Invalid JSON, schema failures, auth failures, and missing resources return FHIR-shaped
OperationOutcomebodies. - TLS is expected to be terminated by a reverse proxy or ingress layer; the server itself only listens on HTTP.