This episode guides you through running nats-server directly or via Docker, putting together a minimal configuration, using the nats pub, nats sub, and nats server check commands, then connecting your first client library to the NATS server.

Episode 2 gave you the conceptual map. Episode 3 is where the theory comes to life: you'll actually run the NATS server, connect to it, and send your first message. There's nothing more satisfying than watching a message travel between terminals.
We'll cover three ways to run the server, put together a minimal file-based configuration, use the CLI to publish and subscribe, and then connect your first client library. Let's begin.
The easiest way is to run the binary directly. In your first terminal:
nats-server -p 4222 -m 8222The -p 4222 flag sets the client port, and -m 8222 enables the monitoring port. The server will print a Listening for client connections on 0.0.0.0:4222 line and block the terminal.
If you're using Docker, the command is almost the same. The nats:2.14 container already has monitoring active on port 8222:
docker run -d --name nats -p 4222:4222 -p 8222:8222 nats:2.14The nats:2.14 container runs in the background with the name nats. Check the logs to make sure the server is up:
docker logs nats | head -n 10For a more structured setup, use a configuration file. Create a nats.conf file:
port: 4222
monitor_port: 8222
server_name: server-utama
jetstream {
store_dir: /data/jetstream
}Then run the server with that file:
nats-server -c nats.confThe jetstream block above enables JetStream from the start with the storage directory /data/jetstream. You'll use configuration files often in episodes 12 through 15.
In a second terminal, start a subscriber first:
nats sub hello.worldIn a third terminal, send a message:
nats pub hello.world "Halo dari CLI"The nats pub hello.world "Halo dari CLI" command sends the message, and the subscriber terminal displays Halo dari CLI along with metadata such as timestamp and reply subject. If there's no subscriber at publish time, the message is lost in Core NATS — that's normal and the designed behavior.
To make sure the server is healthy:
nats server checkThe output shows an ok line with server, version, and cluster information. This nats server check command will become your go-to whenever you want to confirm the server is responding.
Tip
Always start the subscriber before the publisher sends messages in Core NATS. A message published with no subscriber is dropped immediately — this isn't a bug, it's a design choice for maximum speed.
Client libraries are the bridge between applications and the server. An example with nats-py:
import asyncio
import nats
async def main():
nc = await nats.connect("nats://localhost:4222")
async def on_message(msg):
print(f"Subject {msg.subject}: {msg.data.decode()}")
await nc.subscribe("hello.world", cb=on_message)
await nc.publish("hello.world", b"Hello dari Python")
await asyncio.sleep(1)
await nc.close()
asyncio.run(main())await nats.connect("nats://localhost:4222") opens the connection, nc.publish("hello.world", b"Hello dari Python") sends a message, and the on_message callback prints the received message. Run it with python main.py and watch the message travel successfully.
In Go, nats.go offers both sync and async APIs:
package main
import (
"log"
"github.com/nats-io/nats.go"
)
func main() {
nc, err := nats.Connect("nats://localhost:4222")
if err != nil {
log.Fatal(err)
}
defer nc.Close()
_, err = nc.Subscribe("hello.world", func(m *nats.Msg) {
log.Printf("Subject %s: %s", m.Subject, string(m.Data))
})
if err != nil {
log.Fatal(err)
}
nc.Publish("hello.world", []byte("Hello dari Go"))
select {}
}nc.Connect("nats://localhost:4222") in Go opens the connection, and nc.Subscribe("hello.world", handler) registers a callback that's invoked on every incoming message.
As the closing practice of this episode, make sure the full flow works:
nats-server -p 4222 -m 8222 &
nats server check
nats pub ping.pong "pong"
nats sub ping.pongIf nats server check returns ok and the ping-pong message is received by the subscriber, you're officially connected to NATS. Server, CLI, and client library all understand each other — the next step is deepening your communication patterns.
Episode 3 took you from zero to your first connection: running nats-server directly or via Docker, putting together a minimal configuration with the jetstream block, using nats pub, nats sub, and nats server check, and connecting your first Python and Go clients.
Key takeaways:
-c nats.conf configuration file.nats server check is the fastest health check.In episode 4 next, we'll discuss subjects & wildcards — the orders.created.eu subject hierarchy, event-driven naming best practices, and the difference between * and > along with their implications for permissions and subscriptions. This is where the art of NATS routing begins to be mastered.