Getting started¶
By the end of this you will have a NATS server running inside your program, a client connected to it without touching the network, and a message going from one to the other.
Nothing needs installing. No broker, no container, no port.
Install¶
The whole thing¶
This uses the constructors directly. A real service uses Register and lets go/controls run the
lifecycle — see embed a server.
package main
import (
"context"
"fmt"
"log/slog"
"time"
"github.com/nats-io/nats.go"
gonats "gitlab.com/phpboyscout/go/nats"
"gitlab.com/phpboyscout/go/nats/client"
"gitlab.com/phpboyscout/go/nats/server"
)
func main() {
ctx := context.Background()
log := slog.Default()
// A server with no listener at all — not even loopback.
srv, err := server.New(ctx, gonats.ServerSettings{InProcessOnly: true})
if err != nil {
panic(err)
}
if err := server.Start(log, srv)(ctx); err != nil {
panic(err)
}
defer server.Stop(log, srv)(ctx)
// A client that reaches it through memory rather than a socket.
cli, err := client.Connect(ctx, gonats.ClientSettings{Name: "tutorial"}, nil,
client.InProcess(srv))
if err != nil {
panic(err)
}
defer cli.Close(ctx)
got := make(chan string, 1)
if _, err := cli.Subscribe("greetings", func(m *nats.Msg) {
got <- string(m.Data)
}); err != nil {
panic(err)
}
if err := cli.Publish(ctx, "greetings", []byte("hello")); err != nil {
panic(err)
}
select {
case msg := <-got:
fmt.Println("received:", msg)
case <-time.After(time.Second):
fmt.Println("nothing arrived")
}
}
Run it and you get received: hello.
What just happened¶
server.New built a real NATS server — the same one you would deploy standalone — and
InProcessOnly told it not to open a listener. At v0.1.0 that is the only kind this module
builds; anything with a host, port or route is refused, because there is no TLS or authentication
configuration to protect it with yet.
server.Start ran it and waited for it to accept connections, so a server that never becomes
ready fails there rather than at your first publish.
client.InProcess(srv) took an in-memory connection from the server rather than dialling an
address. No TCP is involved, not even a loopback hop.
cli.Subscribe bounded the subscription's queue. Left alone, NATS allows 500,000 pending
messages per subscription, which is a limit in the sense that the heat death of the universe is a
deadline.
cli.Publish is the module's own publish path rather than cli.Raw().Publish. It is the one place
header propagation, metrics and deadlines can live, and using it from the start means you never have
to go back and find every call site. ctx here carries no deadline, so it behaves as NATS does and
returns once the client has buffered; give it one and it waits for the server to acknowledge.
cli.Close(ctx) waited for the drain to finish. nats.Conn.Drain() on its own returns
immediately, which would let the process exit while handlers were still running.
Next¶
- Embed a server — with
go/controlsmanaging the lifecycle, which is how a real service does it, and the ordering that makes it work. - Connect to a cluster — the same code, a different address.
- What this module does not do.