4. Persist messages with JetStream
So far your messages have been fleeting: a subscriber that isn't listening at the moment you publish never sees them. In this tutorial you turn on JetStream, create a stream that stores messages durably, publish a few, and then replay them from the very beginning. By the end you'll have proof that the messages survived, sitting in storage, ready to read again.
What you'll need
nats-serverand thenatsCLI installed (from Hello NATS).- A terminal. You'll start the server in one and run commands in another.
Step 1: Start the server with JetStream
Stop any server you started earlier, then start a new one with the -js flag.
That flag turns on JetStream, the subsystem that stores messages.
nats-server -js
You should see a startup log that mentions JetStream, including a line like:
[INF] Starting JetStream
[INF] Server is ready
Leave this running. Open a second terminal for the remaining steps.
Step 2: Create a stream
In the second terminal, create a stream named EVENTS that captures every
subject beginning with events.. The --defaults flag fills in sensible
starting values so you aren't prompted for anything.
- CLI
#!/bin/bash
# Create a stream named EVENTS that captures every subject under events.>
# --defaults fills in sensible starting values so the CLI does not prompt.
nats stream add EVENTS --subjects "events.>" --defaults
You should see output ending with:
Stream EVENTS was created
Your stream now exists and is waiting for messages.
Step 3: Publish a few messages
Publish three messages to subjects under events.. Because each subject matches
the stream, the server stores every one.
- CLI
#!/bin/bash
# Publish three messages. Each subject starts with events. so the EVENTS
# stream captures and stores every one. For each publish the CLI prints a
# "Stored in Stream" line with the assigned sequence, confirming it was stored.
nats pub events.page_loaded '{"page":"/home"}' --jetstream
nats pub events.input_changed '{"field":"email"}' --jetstream
nats pub events.page_loaded '{"page":"/pricing"}' --jetstream
For each publish you should see two lines: one confirming the message was sent, and one confirming the stream stored it with an assigned sequence number:
13:42:01 Published 16 bytes to "events.page_loaded"
13:42:01 Stored in Stream: EVENTS Sequence: 1
13:42:01 Published 17 bytes to "events.input_changed"
13:42:01 Stored in Stream: EVENTS Sequence: 2
13:42:01 Published 19 bytes to "events.page_loaded"
13:42:01 Stored in Stream: EVENTS Sequence: 3
Confirm the stream now holds three messages:
nats stream info EVENTS
You should see a State block reporting three messages:
State:
Messages: 3
Bytes: 159 B
First Sequence: 1 @ 2026-06-09 13:42:01
Last Sequence: 3 @ 2026-06-09 13:42:01
Active Consumers: 0
Number of Subjects: 2
Step 4: Replay the stored messages
Now read the messages back. This replays every message the stream holds, oldest first, starting from the very first one. Nothing is removed: replaying a stream is a read.
- CLI
#!/bin/bash
# Replay every message stored in the stream, oldest first.
# --all start at the first stored message (sequence 1)
# --terminate-at-end stop once all stored messages have been read
nats sub "events.>" --all --terminate-at-end
You should see all three messages, in the order they were published:
[#1] Received JetStream message: stream: EVENTS seq: 1 / pending: 2 / subject: events.page_loaded / time: 2026-06-09 13:42:01
{"page":"/home"}
[#2] Received JetStream message: stream: EVENTS seq: 2 / pending: 1 / subject: events.input_changed / time: 2026-06-09 13:42:01
{"field":"email"}
[#3] Received JetStream message: stream: EVENTS seq: 3 / pending: 0 / subject: events.page_loaded / time: 2026-06-09 13:42:01
{"page":"/pricing"}
The command exits on its own once it's read everything stored. Run it again and you'll see the same three messages: the stream still has them.
What you built
You enabled JetStream, created the EVENTS stream, published three messages into
it, and replayed all three back from storage. And they were still there to read
again afterward.
Next
- Next tutorial: Read a stream with a durable consumer — a reader that tracks what it's processed and resumes after a restart.
- Understand how this works: the JetStream deep dive explains what a stream really is, and Your first stream walks through every value JetStream filled in for you.