콘텐츠로 이동
한국어

NATS

이 콘텐츠는 아직 번역되지 않았습니다.

NatsActor integrates with NATS — the lightweight pub/sub broker. Core NATS is fire-and-forget without durability; for durable streams, see the JetStream variant.

import { ActorSystem, NatsActor, NatsOptions } from 'actor-ts';
const natsOptions = NatsOptions.create()
.withServers(['nats://nats-1:4222', 'nats://nats-2:4222'])
.withName('my-app');
const nats = system.spawn(
() => new NatsActor(
natsOptions,
),
'nats',
);
// Subscribe (wildcards supported):
nats.tell({
kind: 'subscribe',
subject: 'events.>',
target: eventHandler,
});
// Publish:
nats.tell({
kind: 'publish',
publish: {
subject: 'events.user.signup',
payload: JSON.stringify(event),
},
});
interface NatsOptionsType extends BrokerCommonOptionsType {
servers: string[] | string;
name?: string; // client identifier
user?: string;
password?: string;
token?: string;
subscriptions?: { subject: string; target: ActorRef<NatsMessage> }[]; // wired up at connect time
}

subscriptions and the runtime subscribe command feed the same desired set, held by the BrokerActor base rather than by the connection. Practical consequences:

  • Every subscription — configured or added at runtime — is re-established on the new connection after a reconnect.
  • A subscribe sent while the actor is disconnected is applied on the next connect instead of being dropped.
  • { kind: 'unsubscribe', subject } removes the subject for good: it does not come back on the next reconnect, even if it came from subscriptions.
  • Re-subscribing a live subject swaps its target — the previous subscription is dropped first, so exactly one actor receives it.
// Added at runtime; still there after the broker restarts.
nats.tell({ kind: 'subscribe', subject: 'audit.>', target: auditor });
// Point the same subject at a different actor.
nats.tell({ kind: 'subscribe', subject: 'audit.>', target: newAuditor });
// Gone for good.
nats.tell({ kind: 'unsubscribe', subject: 'audit.>' });

Note that this is about the subscription, not the messages: core NATS has no durability, so anything published during the outage is still gone (see the caution below).

NATS uses .-delimited subjects:

events.user.signup
events.user.delete
orders.priority.placed
metrics.gauge.cpu

Subjects are case-sensitive; per convention they’re lowercase + dot-separated.

WildcardMatches
*Exactly one token (matches user in events.user.signup).
>One or more tokens (matches user.signup in events.user.signup).
'events.>' → events.user.signup, events.user.delete, events.order.placed
'events.*.signup' → events.user.signup, events.admin.signup

> can only appear as the last token.

Core NATS models request/reply with a reply subject: the requester publishes with a replyTo subject and listens on it; the responder sends its answer there. There’s no dedicated request command — you compose it from publish / subscribe and the replyTo field.

// Requester — listen on a private reply subject, then publish the
// request with `replyTo` pointing at it:
nats.tell({ kind: 'subscribe', subject: 'reply.balance.42', target: replyHandler });
nats.tell({
kind: 'publish',
publish: {
subject: 'account.balance',
payload: JSON.stringify({ accountId: '42' }),
replyTo: 'reply.balance.42', // responder answers on this subject
},
});
// Responder — subscribe to the request subject, answer on `replyTo`:
class BalanceService extends Actor<NatsMessage> {
constructor(private readonly nats: ActorRef<NatsCommand>) { super(); }
override onReceive(message: NatsMessage): void {
if (!message.replyTo) return;
this.nats.tell({
kind: 'publish',
publish: {
subject: message.replyTo,
payload: JSON.stringify({ balance: 100 }),
},
});
}
}

Inbound NatsMessages carry subject, payload (a Uint8Array), and replyTo (an empty string when the publisher set none). This is the “RPC over NATS” pattern — a few hundred microseconds round-trip on localhost.

Three primary use cases:

  1. High-throughput pub/sub without the operational complexity of Kafka.
  2. Microservices request/reply — synchronous calls between services via subjects.
  3. Lightweight event distribution — fire-and-forget notifications, metrics, log streams.

For durable streams (replay, history, ack semantics), see JetStream below. For cluster-internal pub/sub, DistributedPubSub is simpler.

For NATS with durability, use JetStreamActor:

import { JetStreamActor, JetStreamOptions } from 'actor-ts';
const jetStreamOptions = JetStreamOptions.create()
.withServers(['nats://nats-1:4222'])
.withStream({
name: 'ORDERS',
subjects: ['orders.>'],
storage: 'file',
maxAge: 86_400 * 7 * 1_000_000_000, // 7 days (maxAge is in nanoseconds)
});
const js = system.spawn(
() => new JetStreamActor(
jetStreamOptions,
),
'js',
);

JetStream layers:

  • Stream — durable log, subjects → records.
  • Consumer — read cursor; multiple consumers can read the same stream independently.
  • Ack semantics — like AMQP, consumers ack each message.

Use JetStream when you need:

  • Replay — consumers can rewind to any offset.
  • Persistence — survives broker restart (file-backed storage).
  • Ordered consumption per subject.

Kafka still beats JetStream for massive scale (billions of events / sec across hundreds of partitions). JetStream is the sweet spot for “less than Kafka, more than core NATS.”

Terminal window
npm install nats
# or: bun add nats

The nats package includes both core NATS and JetStream clients.