This episode summarizes the power of the nats CLI for day-to-day operations and writes complete client programs in Go, Python, and Node.js to connect, publish, subscribe, and request.

Episode 5 gave you the communication patterns; episode 6 gives you the toolkit that will accompany you throughout your NATS career. Two main tools: the nats CLI for administration and debugging, and client libraries for writing real applications.
We'll map out the important CLI commands, then write complete programs in Go, Python, and Node.js. This episode is practical — make sure your NATS server is still running.
Three basic commands you already know:
nats pub orders.created "pesanan-1"
nats sub orders.>
nats request auth.login '{"user":"arman"}'nats pub, nats sub, and nats request cover most of your testing needs. Add the --help flag to every subcommand to see its full options.
When JetStream is active, the CLI provides dedicated subcommands:
nats stream ls
nats consumer ls ORDERS
nats account infonats stream ls lists all streams, nats consumer ls ORDERS lists consumers on a given stream, and nats account info shows an account summary including JetStream quotas. All three are the gateway to the observability in episode 18.
Every CLI command accepts consistent connection options:
nats sub orders.> -s nats://localhost:4222
nats pub orders.created "hi" --creds my.creds
nats stream ls --server nats://localhost:4222The -s nats://localhost:4222 flag specifies the server, and --creds my.creds uses JWT credentials. Making a habit of these flags lets you move easily between development and production servers.
All NATS client libraries follow the same pattern: connect, publish, subscribe, request. Let's compare the three main languages side by side.
Go is the language closest to NATS — both were born from the same ecosystem:
package main
import (
"log"
"time"
"github.com/nats-io/nats.go"
)
func main() {
nc, err := nats.Connect("nats://localhost:4222")
if err != nil {
log.Fatal(err)
}
defer nc.Close()
nc.Subscribe("notif.email", func(m *nats.Msg) {
log.Printf("terima: %s", string(m.Data))
})
nc.Publish("notif.email", []byte("selamat datang"))
resp, err := nc.Request("auth.login", []byte("arman"), time.Second)
if err == nil {
log.Printf("reply: %s", string(resp.Data))
}
select {}
}nc.Request("auth.login", []byte("arman"), time.Second) combines request and timeout in a single call. This connect-subscribe-publish-request pattern is the foundation of all Go programs.
The Python client is fully asynchronous and uses asyncio:
import asyncio
import nats
async def main():
nc = await nats.connect("nats://localhost:4222")
async def handler(msg):
print("terima:", msg.data.decode())
await nc.subscribe("notif.email", cb=handler)
await nc.publish("notif.email", b"selamat datang")
reply = await nc.request("auth.login", b"arman", timeout=1)
print("reply:", reply.data)
await nc.close()
asyncio.run(main())await nc.request("auth.login", b"arman", timeout=1) in Python is the equivalent of nc.Request in Go. Because it's async, all calls use await — a pattern worth remembering when writing handlers.
The Node.js client is also async and Promise-based:
import { connect, StringCodec } from "nats";
const nc = await connect({ servers: "nats://localhost:4222" });
const sc = StringCodec();
nc.subscribe("notif.email", (err, msg) => {
console.log("terima:", sc.decode(msg.data));
});
nc.publish("notif.email", sc.encode("selamat datang"));
const reply = await nc.request("auth.login", sc.encode("arman"), { timeout: 1000 });
console.log("reply:", sc.decode(reply.data));await nc.request("auth.login", sc.encode("arman"), { timeout: 1000 }) in Node.js uses a timeout option in milliseconds. All three languages have parallel APIs — once you understand one, the others are just a matter of adjusting syntax.
Info
Don't forget StringCodec in nats.js. In Go and Python messages are sent as []byte and bytes; in Node.js, codecs like StringCodec and JSONCodec convert strings and objects into Uint8Array before publishing.
There's no single answer; each language has its strengths:
need performance & mature ecosystem -> Go
quick prototyping & data pipeline -> Python
team is Node.js, I/O-heavy -> Node.jsThe need performance & mature ecosystem → Go rule isn't dogma — but it's the direction the community chooses most often. What matters most: understand the client library patterns, because all three are consistent.
Episode 6 equipped you fully: the nats CLI for daily operations (pub, sub, request, stream, consumer, account) with consistent connection options, plus complete client programs in Go, Python, and Node.js that follow the identical connect, publish, subscribe, and request pattern.
Key takeaways:
-s, --creds, and --timeout flags are consistent across all subcommands.In episode 7 next, we'll discuss JetStream: introduction — the concept of a stream as a persistent log, retention, storage, and durability, how to enable JetStream with the -js flag or a configuration block, and the flow from publishing into a stream and from a stream to a consumer. This is where NATS starts to gain long-term memory.