Skip to content

NATS Reference: Pub/Sub, JetStream Streams, KV Store, Clustering & Request/Reply

NATS is a lightweight, high-performance messaging system — simpler than Kafka/RabbitMQ for most use cases. It supports pub/sub, request/reply, queue groups, and JetStream (persistent, ordered streams with at-least-once delivery).

1. NATS vs Kafka vs RabbitMQ

When to choose NATS
Feature NATS Kafka RabbitMQ
Persistence JetStream (optional) Always (append-only log) Durable queues
Protocol NATS native (TCP, WebSocket) Kafka binary protocol AMQP
Resource usage Very low (~10MB server) High (JVM, ZooKeeper/KRaft) Medium (Erlang)
Latency Sub-millisecond Single-digit ms Low ms
Core pattern Pub/Sub, Request/Reply, Queue Groups Consumer group offset Exchange + queue routing
Clustering Built-in (Raft, 3+ nodes) Kafka native or KRaft RabbitMQ clustering
Best for Microservice RPC, IoT, low-overhead pub/sub Event streaming, audit logs Task queues, complex routing
# Rule of thumb:
# NATS: you want fast pub/sub or request/reply with minimal ops overhead
# Kafka: you need event replay, large-scale streaming, or audit logs
# RabbitMQ: you need dead-letter queues, complex routing, or traditional task queues

2. Core Messaging Patterns

Pub/sub, request/reply, and queue groups
# nats CLI (install: brew install nats-io/nats-tools/nats):
nats sub "orders.>"                  # subscribe to all orders.* subjects
nats pub orders.created '{"id": 123}'   # publish
nats req orders.validate '{"id": 123}'  # request/reply (waits for response)

# Subject naming:
# "." = separator (hierarchical)
# "*" = one token wildcard: orders.*.created matches orders.us.created
# ">" = multi-token wildcard: orders.> matches everything under orders

# Pub/Sub (fire and forget, no persistence):
# Publisher sends to a subject
# All subscribers to that subject receive a copy (fanout)
# No persistence — if subscriber is offline, message is lost

# Request/Reply (synchronous pattern):
# Requester publishes to a subject + sets reply-to header
# Responder receives, publishes response to reply-to address
# Used for microservice RPC without a gateway

# Queue Groups (competing consumers — like Kafka consumer groups):
# Multiple subscribers with same queue group → each message delivered to ONLY ONE
nats sub --queue mygroup orders.created   # subscriber 1
nats sub --queue mygroup orders.created   # subscriber 2
nats pub orders.created '{"id": 1}'       # delivered to either subscriber 1 OR 2

3. JetStream — Persistent Messaging

Streams, consumers, and at-least-once delivery
# JetStream adds persistence to NATS (disabled by default — enable in server config)
# nats-server --jetstream

# Create a stream (like a Kafka topic):
nats stream add ORDERS   --subjects "orders.>" \          # capture all orders.* subjects
  --retention limits \             # keep until limits reached
  --max-age 24h \                  # messages expire after 24h
  --storage file \                 # file (disk) or memory
  --replicas 3                     # replicate across 3 nodes

# Or via YAML:
nats stream add ORDERS --config stream-config.yaml

# Consumer types:
# Push consumer: NATS pushes messages to subscriber (like pub/sub but with ack)
# Pull consumer: subscriber explicitly requests batches (like Kafka)

# Create a durable pull consumer:
nats consumer add ORDERS my-consumer   --pull   --deliver all \                  # start from beginning
  --ack explicit                   # must ack each message

# Pull messages:
nats consumer next ORDERS my-consumer --count 10   # pull 10 messages

# Push consumer (messages delivered automatically):
nats consumer add ORDERS push-consumer   --deliver all   --ack explicit   --target notifications.orders    # deliver to this subject

# List streams and consumers:
nats stream ls
nats stream info ORDERS
nats consumer ls ORDERS
nats consumer info ORDERS my-consumer

4. Key-Value Store (JetStream KV)

Distributed KV with watch and history
# JetStream includes a KV store backed by streams
# Great for: config management, feature flags, service discovery

# Create a KV bucket:
nats kv add CONFIG --ttl 24h --history 5   # 5 revisions history, 24h TTL

# Basic ops:
nats kv put CONFIG app.log_level "info"
nats kv get CONFIG app.log_level
nats kv del CONFIG app.log_level
nats kv ls CONFIG                           # list all keys
nats kv history CONFIG app.log_level       # show all revisions

# Watch for changes (blocks until key changes):
nats kv watch CONFIG app.log_level         # watch one key
nats kv watch CONFIG                       # watch all keys

# Compare-and-set (atomic update):
nats kv update CONFIG app.log_level "debug" --revision 3
# Fails if current revision != 3 (someone else updated it)

# Object Store (JetStream ObjStore — for larger binary blobs):
nats object add ARTIFACTS
nats object put ARTIFACTS my-binary ./binary-file
nats object get ARTIFACTS my-binary -O /tmp/downloaded

5. NATS Server Configuration

Clustering, authentication, and K8s deployment
# nats-server config (nats.conf):
port: 4222
http_port: 8222                    # monitoring endpoint (/healthz, /varz, /connz)

jetstream {
  store_dir: /data/nats/jetstream
  max_memory_store: 1Gi
  max_file_store: 10Gi
}

# TLS:
tls {
  cert_file: /etc/nats/tls/tls.crt
  key_file:  /etc/nats/tls/tls.key
  ca_file:   /etc/nats/tls/ca.crt
  verify: true                     # require client certs (mTLS)
}

# Authentication (token or user/password):
authorization {
  token: "my-secret-token"
  # OR:
  users: [{user: myapp, password: secret}]
}

# Cluster config (3-node example):
cluster {
  name: my-cluster
  port: 6222
  routes: [
    nats-route://nats-0:6222,
    nats-route://nats-1:6222,
    nats-route://nats-2:6222
  ]
}

# Docker Compose (dev):
services:
  nats:
    image: nats:2.10-alpine
    command: ["--jetstream", "--http_port", "8222"]
    ports:
      - "4222:4222"   # client
      - "8222:8222"   # monitoring

# K8s with Helm:
helm repo add nats https://nats-io.github.io/k8s/helm/charts/
helm install nats nats/nats   --set config.jetstream.enabled=true   --set config.cluster.enabled=true   --set reloader.enabled=true

6. Observability & Debugging

Monitoring endpoints, nats CLI ops, and common issues
# Server monitoring (JSON endpoints):
curl http://nats:8222/healthz      # health check
curl http://nats:8222/varz         # server stats (connections, msgs/sec, memory)
curl http://nats:8222/connz        # active connections
curl http://nats:8222/subz         # active subscriptions
curl http://nats:8222/jsz          # JetStream stats (streams, consumers, bytes)

# nats CLI monitoring:
nats server info                   # server version, connections, JetStream stats
nats server list                   # cluster members
nats server report connections     # connection details
nats server report jetstream       # JetStream cluster health

# Stream health:
nats stream report                 # all streams: messages, bytes, consumers, cluster
nats stream get ORDERS 42          # get message by sequence number
nats stream purge ORDERS           # delete all messages (DANGEROUS)

# Common issues:
# - Consumer not receiving: check consumer is durable + stream subject filter matches
# - Messages dropped: check stream max-msgs or max-bytes limits
# - JetStream unavailable: check js.enabled in server config + sufficient resources
# - Cluster split-brain: always use odd number of nodes (3 or 5)

Track NATS, Kafka, and messaging tool releases.
ReleaseRun monitors Kubernetes, Docker, and 13+ DevOps technologies.

Related: Apache Kafka Reference | RabbitMQ Reference | Kubernetes YAML Reference

🔍 Free tool: K8s YAML Security Linter — if you deploy NATS on Kubernetes, check your manifests for 12 security misconfigurations.

Founded

2023 in London, UK

Contact

hello@releaserun.com