Define PubSub and Subscription interfaces in the core via package with a vianats sub-package providing the embedded NATS + JetStream implementation. Expose c.Publish() and c.Subscribe() on Context with automatic subscription cleanup on session close. Refactor the NATS chatroom example to use the built-in methods instead of manual subscription tracking.
NATS Chatroom Example (Embedded)
A chatroom built with Via and an embedded NATS server, demonstrating pub/sub messaging as an alternative to the custom Rooms implementation in ../chatroom.
Uses delaneyj/toolbelt/embeddednats to run NATS inside the same binary - no external server required.
Key Differences from Original Chatroom
| Aspect | Original (../chatroom) |
This Example |
|---|---|---|
| Pub/sub | Custom Rooms struct (~160 lines) |
NATS subjects |
| Member tracking | Manual map[TU]Syncable |
NATS handles subscribers |
| Publish timing | Ticker every 100ms + dirty flag | Instant delivery |
| Durability | None (in-memory) | JetStream persists to disk |
| Multi-instance | Not supported | Works across server instances |
| External deps | None | None (NATS embedded in binary) |
Run the Example
go run ./internal/examples/nats-chatroom
That's it. No separate NATS server needed.
Open multiple browser tabs at http://localhost:7331 to see messages broadcast across all clients.
How Embedded NATS Works
// Start embedded NATS server (JetStream enabled by default)
ns, err := embeddednats.New(ctx,
embeddednats.WithDirectory("./data/nats"),
)
ns.WaitForServer()
// Get client connection to embedded server
nc, err := ns.Client()
Data is persisted to ./data/nats/ for JetStream durability.
Architecture
┌─────────────────────────────────────────────────────────┐
│ Single Binary │
│ │
│ Browser A Embedded NATS Browser B │
│ │ │ │ │
│ │-- Via Action ---> │ │ │
│ │ (Send msg) │ │ │
│ │ │ │ │
│ │ nc.Publish() │ │
│ │ "chat.room.Go" │ │
│ │ │ │ │
│ │<-- Subscribe -----|---- Subscribe --->│ │
│ │ callback │ callback │ │
│ │ │ │ │
│ │-- c.Sync() ------>│<--- c.Sync() -----| │
│ │ (SSE) │ (SSE) │ │
│ │
└─────────────────────────────────────────────────────────┘
JetStream Durability
Messages persist to disk via JetStream:
js.AddStream(&nats.StreamConfig{
Name: "CHAT",
Subjects: []string{"chat.>"},
MaxMsgs: 1000, // Keep last 1000 messages
MaxAge: 24 * time.Hour,
})
Stop and restart the app - chat history survives.
Code Comparison
Original chatroom - 160+ lines of custom pub/sub:
Roomsstruct with named roomsRoomwith member tracking, mutex, dirty flag- Ticker-based publish loop
- Manual join/leave channels
This example - ~60 lines of NATS integration:
embeddednats.New()starts the servernc.Subscribe(subject, handler)for receivingnc.Publish(subject, data)for sending- NATS handles delivery, no polling
Next Steps
If this pattern proves useful, it could be promoted to a Via plugin:
// Hypothetical future API
v.Config(via.WithEmbeddedNATS("./data/nats"))
// In page init
c.Subscribe("events.user.*", func(data []byte) {
c.Sync()
})
c.Publish("events.user.login", userData)