Objectives
Learn how to integrate with core NATS / NATS JetStream
Before you begin
Make sure your Aidbox version is newer than 2504
Setup the local Aidbox instance using getting started
. Make sure that go packages are in your $PATH variable.
What is NATS?
is a high-performance, open-source messaging broker designed for communication between services and microservices. It provides simple and fast message delivery using the publish/subscribe (pub/sub) pattern and supports various application interaction scenarios.
JetStream vs Core NATS
Core NATS is a lightweight pub/sub system where messages are delivered to subscribers only at the moment of publishing (best-effort delivery). If a subscriber is offline, the message is lost.
JetStream is an extension of NATS that provides message persistence, replay, and acknowledgment (at least once delivery). It allows you to create streams, store messages, manage consumers, and ensure that important events are not lost even in the case of failure.
In Aidbox, create with http://aidbox.app/StructureDefinition/aidboxtopicdestination-nats-core-best-effort
profile to integrate with Core NATS and http://aidbox.app/StructureDefinition/aidboxtopicdestination-nats-jetstream-at-least-once
to integrate with NATS JetStream.
Setting up
Create a directory structure like this:
Copy .
├── docker-compose.yaml
├── jetstream/
├── jwt-auth/
└── username-password/
Copy mkdir nats && cd nats
mkdir jetstream
mkdir jwt-auth
mkdir username-password
Copy aidbox:
extra_hosts:
# connect Aidbox container and NATS from localhost
- "host.docker.internal:host-gateway"
volumes:
# module jar to turn on nats support
- ./topic-destination-nats-2505.2.jar:/topic-destination-nats-2505.2.jar
# creds used in authentication
- ./jwt-auth/creds:/creds
# ...
environment:
BOX_MODULE_LOAD: io.healthsamurai.topic-destination.nats.core
BOX_MODULE_JAR: "/topic-destination-nats-2505.2.jar"
# ... other envs ...
Copy curl -O https://storage.googleapis.com/aidbox-modules/topic-destination-nats/topic-destination-nats-2505.2.jar
Copy go install github.com/nats-io/natscli/nats@latest
go install github.com/nats-io/nats-server/v2@latest
go install github.com/nats-io/nsc/v2@latest
Now, in AidboxUI, go to FHIR Packages -> io.healthsamurai.topic and make sure that NATS profiles are present.
Basic Usage
Subscribe to the foo
subject.
Check that publishing and subscribing work.
Copy nats pub foo "Hello, NATS"
Go to AidboxUI and create a topic that triggers if Patient.name
exists.
Copy POST /fhir/AidboxSubscriptionTopic
content-type: application/json
accept: application/json
{
"resourceType": "AidboxSubscriptionTopic",
"url": "patient-topic",
"status": "active",
"trigger": [
{
"resource": "Patient",
"fhirPathCriteria": "name.exists()"
}
]
}
Create AidboxTopicDestination with http://aidbox.app/StructureDefinition/aidboxtopicdestination-nats-core-best-effort
profile.
Copy POST /fhir/AidboxTopicDestination
content-type: application/json
accept: application/json
{
"id": "basic",
"resourceType": "AidboxTopicDestination",
"meta": {
"profile": [
"http://aidbox.app/StructureDefinition/aidboxtopicdestination-nats-core-best-effort"
]
},
"kind": "nats-core-best-effort",
"topic": "patient-topic",
"parameter": [
{
"name": "url",
"valueString": "nats://host.docker.internal:4222"
},
{
"name": "subject",
"valueString": "foo"
},
{
"name": "sslContext",
"valueString": "none"
}
]
}
Create a patient with a name.
Copy POST /fhir/Patient
name:
- family: smith
See the output in your terminal that the Patient is created.
Copy [#3] Received on "foo"
content-type: application/json
{"topic":"foo","value":{"resourceType":"Bundle","type":"history","timestamp":"2025-05-05T09:54:29Z","entry":[{"resource":{"resourceType":"AidboxSubscriptionStatus","status":"active","type":"event-notification","notificationEvent":[{"eventNumber":1,"focus":{"reference":"Patient/5e78f7b8-e6dc-404b-93b5-2356ed1e48b6"}}],"topic":"patient-topic","topic-destination":{"reference":"AidboxTopicDestination/basic"}}},{"request":{"method":"POST","url":"/fhir/Patient"},"fullUrl":"http://localhost:8080/fhir/Patient/5e78f7b8-e6dc-404b-93b5-2356ed1e48b6","resource":{"name":[{"family":"smith"}],"id":"5e78f7b8-e6dc-404b-93b5-2356ed1e48b6","resourceType":"Patient","meta":{"lastUpdated":"2025-05-05T09:54:29.496342Z","versionId":"17","extension":[{"url":"https://aidbox.app/ex/createdAt","valueInstant":"2025-05-05T09:54:29.496342Z"}]}}}]}}
NATS JetStream
Change working directory.
Copy nats stream add EVENTS \
--subjects="patients.>" \
--storage=file \
--defaults
Publish a message to the stream.
Copy nats pub patients.test "hello JetStream"
Check the stream saves the message.
The output:
Copy ╭─────────────────────────────────────────────────────────────────────────────╮
│ Streams │
├────────┬─────────────┬─────────────────────┬──────────┬──────┬──────────────┤
│ Name │ Description │ Created │ Messages │ Size │ Last Message │
├────────┼─────────────┼─────────────────────┼──────────┼──────┼──────────────┤
│ EVENTS │ │ 2025-05-05 13:49:16 │ 1 │ 58 B │ 2.14s │
╰────────┴─────────────┴─────────────────────┴──────────┴──────┴──────────────╯
Create AidboxTopicDestination with http://aidbox.app/StructureDefinition/aidboxtopicdestination-nats-jetstream-at-least-once
profile.
Copy POST /fhir/AidboxTopicDestination
content-type: application/json
accept: application/json
{
"resourceType": "AidboxTopicDestination",
"meta": {
"profile": [
"http://aidbox.app/StructureDefinition/aidboxtopicdestination-nats-jetstream-at-least-once"
]
},
"kind": "nats-jetstream-at-least-once",
"id": "jetstream",
"topic": "patient-topic",
"parameter": [
{
"name": "url",
"valueString": "nats://host.docker.internal:4222"
},
{
"name": "subject",
"valueString": "patients.created"
},
{
"name": "sslContext",
"valueString": "none"
}
]
}
Copy POST /fhir/Patient
name:
- family: smith
Create a stream consumer.
Copy nats consumer add EVENTS my-consumer --defaults --pull
Pull the first available message.
Copy nats consumer next EVENTS my-consumer
It is our message published from CLI:
Copy [13:59:58] subj: patients.test / tries: 1 / cons seq: 1 / str seq: 1 / pending: 1
hello JetStream
Acknowledged message
Copy nats consumer next EVENTS my-consumer
It is the message from Aidbox:the
Copy [14:00:21] subj: patients.created / tries: 1 / cons seq: 2 / str seq: 2 / pending: 0
Headers:
content-type: application/json
Data:
{"topic":"patients.created","value":{"resourceType":"Bundle","type":"history","timestamp":"2025-05-05T10:57:54Z","entry":[{"resource":{"resourceType":"AidboxSubscriptionStatus","status":"active","type":"event-notification","notificationEvent":[{"eventNumber":1,"focus":{"reference":"Patient/598ef035-89f7-4b19-9ad7-fa4ad8f38681"}}],"topic":"patient-topic","topic-destination":{"reference":"AidboxTopicDestination/jetstream"}}},{"request":{"method":"POST","url":"/fhir/Patient"},"fullUrl":"http://localhost:8080/fhir/Patient/598ef035-89f7-4b19-9ad7-fa4ad8f38681","resource":{"name":[{"family":"smith"}],"id":"598ef035-89f7-4b19-9ad7-fa4ad8f38681","resourceType":"Patient","meta":{"lastUpdated":"2025-05-05T10:57:54.908063Z","versionId":"35","extension":[{"url":"https://aidbox.app/ex/createdAt","valueInstant":"2025-05-05T10:57:54.908063Z"}]}}}]}}
Acknowledged message
Username/Password authentication
Turn off the previous nats-server (Ctrl+C
).
Change working directory.
Create new nats-server.conf
file.
Copy port: 4222
authorization {
users = [
{
user: "alice"
password: "secret1"
permissions = {
publish = ["mysubject.>"]
subscribe = []
}
},
{
user: "bob"
password: "secret2"
permissions = {
publish = []
subscribe = ["mysubject.>"]
}
}
]
}
Start nats with this config:
Copy nats-server -c nats-server.conf
Copy nats --user bob --password secret2 sub mysubject.hello
Check that alice can publish:
Copy nats --user alice --password secret1 pub mysubject.hello "hello from alice"
Add username
and password
properties in AidboxTopicDestination
resource.
Copy POST /fhir/AidboxTopicDestination
content-type: application/json
accept: application/json
{
"resourceType": "AidboxTopicDestination",
"meta": {
"profile": [
"http://aidbox.app/StructureDefinition/aidboxtopicdestination-nats-core-best-effort"
]
},
"kind": "nats-core-best-effort",
"id": "nats-core-destination",
"topic": "patient-topic",
"parameter": [
{
"name": "url",
"valueString": "nats://host.docker.internal:4222"
},
{
"name": "subject",
"valueString": "mysubject.hello"
},
{
"name": "username",
"valueString": "alice"
},
{
"name": "password",
"valueString": "secret1"
},
{
"name": "sslContext",
"valueString": "none"
}
]
}
Post the patient with a name.
Copy POST /fhir/Patient
name:
- family: smith
See the output of bob's subscription.
Copy {"topic":"mysubject.hello","value":{"resourceType":"Bundle","type":"history","timestamp":"2025-05-21T13:36:33Z","entry":[{"resource":{"resourceType":"AidboxSubscriptionStatus","status":"active","type":"event-notification","notificationEvent":[{"eventNumber":1,"focus":{"reference":"Patient/89ae88de-c15b-4aac-a393-9168f2dc507c"}}],"topic":"patient-topic","topic-destination":{"reference":"AidboxTopicDestination/nats-core-destination"}}},{"request":{"method":"POST","url":"/fhir/Patient"},"fullUrl":"http://localhost:8080/fhir/Patient/89ae88de-c15b-4aac-a393-9168f2dc507c","resource":{"name":[{"family":"smith"}],"id":"89ae88de-c15b-4aac-a393-9168f2dc507c","resourceType":"Patient","meta":{"lastUpdated":"2025-05-21T13:36:33.378705Z","versionId":"14","extension":[{"url":"ex:createdAt","valueInstant":"2025-05-21T13:36:33.378705Z"}]}}}]}}
JWT authentication
Turn off the previous nats-server (Ctrl+C
).
Change working directory.
Set up NATS operator to use the generated signing key:
Copy nsc add operator --generate-signing-key --sys --name local
nsc edit operator --require-signing-keys \
--account-jwt-server-url "nats://localhost:4222"
Create APP account and generate a new signed key for it.
Copy nsc add account APP
nsc edit account APP --sk generate
Create server.conf and resolver.conf files
Copy nsc generate config --nats-resolver --sys-account SYS > resolver.conf
cat <<- EOF > server.conf
include resolver.conf
EOF
Now the server config includes your operator, accounts, and JWT resolver settings.
Upload your APP account to the NATS server, so NATS knows what APP is.
Create user joe
and save it in the APP. Joe can publish to subjects that start with "joe".
Copy nsc add user --account APP joe --allow-pub 'joe.>'
nats context save joe --nsc nsc://local/APP/joe
Do the same to pam
. Pam can publish to subjects that start with "pam".
Copy nsc add user --account APP pam --allow-pub 'pam.>'
nats context save pam --nsc nsc://local/APP/pam
Create user admin
that can do everything.
Copy nsc add user --account APP admin \
--allow-pub '>' \
--allow-sub '>'
nats context save admin --nsc nsc://local/APP/admin
Move creds next to docker-compose.yaml file to mount them to use in Aidbox.
Copy mkdir -p creds
# make sure the path to creds is right
cp -r ~/.local/share/nats/nsc/keys/creds/local/APP/* creds
Copy nats-server -c server.conf
Try to push to joe.message
using Joe's credentials.
Copy nats pub joe.message "hello from joe" --context joe
# same thing using --creds
# nats pub joe.message "hello from joe" --creds creds/joe.creds
Try to push to pam.message
using Joe's credentials.
Copy nats pub pam.message "hello from joe" --context joe
Access denied, the error:
Copy nats: error: nats: permissions violation: Permissions Violation for Publish to "pam.message"
Create joe-to-pam
AidboxTopicDestination. Note that Aidbox, as a Client, has no idea about rights in best-effort mode.
Copy POST /fhir/AidboxTopicDestination
content-type: application/json
accept: application/json
{
"id": "joe-to-pam",
"resourceType": "AidboxTopicDestination",
"meta": {
"profile": [
"http://aidbox.app/StructureDefinition/aidboxtopicdestination-nats-core-best-effort"
]
},
"kind": "nats-core-best-effort",
"topic": "patient-topic",
"parameter": [
{
"name": "url",
"valueString": "nats://host.docker.internal:4222"
},
{
"name": "subject",
"valueString": "pam.a"
},
{
"name": "connectionName",
"valueString": "connectionname"
},
{
"name": "credentialsFilePath",
"valueString": "/creds/joe.creds"
},
{
"name": "sslContext",
"valueString": "none"
}
]
}
Use Joe's credentials to publish to joe
subject.
Copy POST /fhir/AidboxTopicDestination
content-type: application/json
accept: application/json
{
"id": "joe-to-joe",
"resourceType": "AidboxTopicDestination",
"meta": {
"profile": [
"http://aidbox.app/StructureDefinition/aidboxtopicdestination-nats-core-best-effort"
]
},
"kind": "nats-core-best-effort",
"topic": "patient-topic",
"parameter": [
{
"name": "url",
"valueString": "nats://host.docker.internal:4222"
},
{
"name": "subject",
"valueString": "joe.message"
},
{
"name": "connectionName",
"valueString": "connectionname"
},
{
"name": "credentialsFilePath",
"valueString": "/creds/joe.creds"
},
{
"name": "sslContext",
"valueString": "none"
}
]
}
From your terminal, subscribe to joe.message
from admin.
You can try to subscribe to the subject using Joe's or Pam's credentials, but they do not have the right to do so.
Post the patient with a name.
Copy POST /fhir/Patient
name:
- family: smith
See the output in the terminal.
The error from joe-pam TopicDestination trying to publish without rights:
Copy Publish Violation - Nkey "UDVE47SBIUZZM76JDTR6T2RBYF2AANUZH7WTJZYHHZF36GACGXQUGMUF", Subject "pam.a"