skip to content
szczecin 53.43N 14.55E ./start-project

Bridging NATS to the browser with one Caddy directive

(nats, go, open-source) 1 min read by Bartosz Pachołek

How NUTS turns NATS JetStream subjects into Server-Sent Events, and why a Caddy module was the right home for it.

Browsers already speak one long-lived protocol that needs no library: Server-Sent Events. NATS is great at moving messages between services. NUTS connects the two inside the web server you may already run, as a Caddy module.

One directive

caddyfile
:8080 {
  route /events* {
    uri strip_prefix /events
    nuts {
      nats_url     nats://nats:4222
      stream_name  EVENTS
      topic_prefix events.
    }
  }
}

A browser subscribes with the standard API, to one topic or several:

js
const es = new EventSource('/events?topic=orders&topic=payments');
es.addEventListener('message', (e) => console.log(JSON.parse(e.data)));

Why JetStream

Messages are read from a JetStream stream, so retention and storage stay in NATS and every event has a sequence number. That number is the SSE id; when a browser reconnects it sends Last-Event-ID, and NUTS replays what it missed. Slow clients are disconnected cleanly so they can reconnect and replay.

Who may listen

Subscriber auth uses HMAC-signed JWTs with a subscribe claim listing the topics a client may stream, read from a header or, for EventSource, from a cookie. Prometheus metrics and TLS or mTLS to NATS are built in.

Where it runs

This site uses it: new contact messages and content changes reach the admin panel live through NUTS. Build it with xcaddy build --with github.com/ideaconnect/nuts or run the idcttech/nuts Docker image.