Updated Nov 5, 20265 min read

Best Go Framework for Event-Driven Systems in 2026

Watermill, the NATS client and kafka-go compared for event-driven Go services, plus how Encore declares topics and subscriptions in Go and provisions the broker in your own AWS or GCP account.

Illustration for Best Go Framework for Event-Driven Systems in 2026

Event-driven design lets services react to what happened without calling each other directly, and Martin Fowler's What do you mean by "Event-Driven"? separates event notification, event-carried state transfer and event sourcing, which tend to get mixed together under one name.

Go has strong broker clients for this, and most of the choice is between a library that abstracts the broker and a client for one specific broker. Where the broker runs is a separate question, and it decides what delivery semantics you get: Redis Pub/Sub, for example, documents at-most-once delivery, while Kafka keeps events on disk for replay.

What to weigh when choosing:

  • Whether you want one interface over several brokers or a direct client.
  • How retries, dead-lettering and acknowledgement are handled.
  • Whether events are typed in Go or raw bytes you decode.
  • Who runs the broker locally and in production.

Go event-driven options at a glance

OptionApproachBest fit
WatermillPublisher and Subscriber interfaces over many brokers, with routers and middlewareTeams that want broker independence and CQRS helpers
nats.goOfficial NATS client with JetStreamLow-latency messaging on NATS
kafka-goKafka client with readers and writersEvent streams on Kafka
Compare features across all three options
CapabilityWatermillnats.gokafka-go
Typed eventsBytes, with marshalersBytesBytes
RetriesRetry middlewareJetStream redeliveryYou implement them
BrokerYour choiceNATSKafka
Local infrastructureYou run the brokerYou run NATSYou run Kafka
Production infrastructureYou provision itYou provision itYou provision it

Watermill: one interface for many brokers

Watermill

Watermill provides Publisher and Subscriber interfaces with implementations for Kafka, NATS, RabbitMQ, SQL databases and cloud brokers, plus a router with middleware.

Key features

  • Swap brokers without changing handlers.
  • Router with retry, poison queue and correlation middleware.
  • CQRS and event-sourcing helpers.

Good to know

Messages are bytes with marshalers on top, and the broker itself is still yours to run.

Try Watermill →

nats.go: low-latency messaging

nats.go

The official NATS Go client supports core publish and subscribe, request-reply and JetStream streams and consumers.

Key features

  • Request-reply in the client.
  • JetStream for persistence, replay and acknowledgements.
  • Key-value and object stores built on JetStream.

Good to know

Core NATS is fire-and-forget, so delivery guarantees require JetStream configuration.

Try nats.go →

kafka-go: event streams on Kafka

kafka-go

kafka-go from Segment offers readers and writers that follow Go's io conventions, with consumer groups.

Key features

  • Reader and Writer types with consumer groups.
  • Pure Go, without a C dependency.
  • Offset control for replay.

Good to know

Retries and dead-letter handling are yours to implement, and Kafka is a heavy service to operate yourself.

Try kafka-go →

Encore: topics declared in Go

Encore

Encore combines event handling in Go with the broker behind it through its Go backend framework with built-in infrastructure, where a topic is a typed package-level variable and a subscription is a declaration with a handler. encore run starts a local Pub/Sub broker and Postgres, and a deploy provisions SNS and SQS on AWS or Pub/Sub on Google Cloud in your own account. Separately from the client library, Encore can declare topics in Go and provision SNS/SQS or Pub/Sub in your account, whichever option above you pick.

package billing import ( "context" "time" "encore.dev/pubsub" "encore.dev/rlog" ) type InvoicePaid struct { InvoiceID string Amount int64 } var InvoicePaidTopic = pubsub.NewTopic[*InvoicePaid]("invoice-paid", pubsub.TopicConfig{ DeliveryGuarantee: pubsub.AtLeastOnce, }) var _ = pubsub.NewSubscription(InvoicePaidTopic, "update-ledger", pubsub.SubscriptionConfig[*InvoicePaid]{ Handler: UpdateLedger, RetryPolicy: &pubsub.RetryPolicy{ MinBackoff: time.Second, MaxRetries: 5, }, }) func UpdateLedger(ctx context.Context, e *InvoicePaid) error { rlog.Info("recording payment", "invoice", e.InvoiceID, "amount", e.Amount) return nil } func MarkPaid(ctx context.Context, id string, amount int64) error { _, err := InvoicePaidTopic.Publish(ctx, &InvoicePaid{InvoiceID: id, Amount: amount}) return err }
The event type is shared by publisher and subscriber, so a field change breaks the build rather than a consumer.

Key features

  • Topics and subscriptions are typed, so publishers and subscribers agree on the event at compile time.
  • Retry policies sit in the subscription declaration.
  • Tracing links the publishing request to each subscriber run.
  • An agent adding a new consumer writes one more NewSubscription, and tests in encore test run it against local infrastructure.

Good to know

Encore's Pub/Sub API covers topics and subscriptions with at-least-once delivery, or exactly-once within the provider's throughput limits, and it has no offset or seek API, so Kafka-style replay of past events is not part of it. Kafka's own replay is bounded by topic retention, seven days by default according to the log.retention.hours broker setting, and compacted topics keep only the latest value per key. Cloud resources are created at deploy time, and the cloud access sits with Encore's IAM role, not the agent. Each subscription is also listed in the service catalog.

Try Encore →

How to choose

  • Watermill for broker independence and CQRS helpers.
  • nats.go for low-latency messaging and request-reply.
  • kafka-go for replayable event streams on Kafka.

Frequently asked questions

What is the best Go framework for event-driven systems?

Watermill is the most complete Go library for event-driven code, with one Publisher and Subscriber interface over Kafka, NATS, RabbitMQ, SQL and cloud brokers. The NATS client and kafka-go are direct clients for one broker each. Encore is not a competing framework but a backend framework with built-in infrastructure where topics and subscriptions are declared in Go and the broker is provisioned for you, using SNS and SQS on AWS or Pub/Sub on Google Cloud.

Kafka or NATS for Go services?

Kafka suits high-volume event streams that consumers replay from a log. NATS suits low-latency messaging and request-reply, with JetStream adding persistence. Both have mature Go clients.

How do I add a Pub/Sub subscriber to an Encore Go service?

Declare the subscription at package level with pubsub.NewSubscription, passing the topic, a unique subscription name and a SubscriptionConfig whose Handler is a function taking a context and the event. Encore starts the subscriber with the service, retries failed messages, and provisions the subscription with the topic.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders