Model or dataset
tmaxmax/go-sse avatar
tmaxmax/go-sse

go-sse is a server-sent events library with both ends of the protocol in Go

Fully featured, spec-compliant HTML5 server-sent events library

532 stars35 forksGoMIT

At a glance

What is it?
go-sse implements HTML5 server-sent events on both sides, with a pluggable publish-subscribe Provider so you can back the event distribution with Redis, Kafka or RabbitMQ instead of the built-in implementation. It suits a Go developer streaming events from a server to a browser, and it doubles as a client for reading other servers' event streams.
Who is it for?
go-sse fits a Go service that needs to push events to browsers over HTTP without adding a websocket layer, and it fits an LLM SDK that needs to read a streaming API response. It does not fit a codebase still on Go 1.21 or older, since the iterator syntax depends on Go 1.23 range-over-func, and it does not fit a deployment that needs horizontal scaling without thinking about the Provider.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 14 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Both ends of the protocol, and why that matters

go-sse is a lightweight, fully spec-compliant HTML5 server-sent events library, and the first structural thing to know is that it implements the protocol on both sides.

The library provides both server-side and client-side implementations of the protocol. The implementations are completely decoupled and unopinionated: you can connect to a server created using go-sse from the browser, and you can connect to any server that emits events using the client.

That second half is often overlooked and is arguably the more immediately useful half. Reading an event stream from someone else's server means handling the wire format correctly, and most implementations of that are duplicated across every SDK that needs it. The library makes that explicit as a use case, saying sse.Read is also useful if you are implementing an LLM SDK: call it in your code and spare yourself time and maintenance burden by not reimplementing event stream parsing.

The installation is a single go get.

sh
go get -u github.com/tmaxmax/go-sse

And the README carries an important warning about the master branch. It is strongly recommended to use tagged versions in your projects, because the master branch has tested but unreleased and maybe undocumented changes, which may break backwards compatibility. Use with caution.

The library promises to support the Go versions supported by the Go team, which is the two most recent major releases.

Reading an LLM stream with a range loop

The README leads with the most immediately practical case, and it is a good demonstration of the client API.

go
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.yourllm.com/v1/chat/completions", payload)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+yourKey)

res, err := http.DefaultClient.Do(req)
if err != nil {
    // handle error
}
defer res.Body.Close() // don't forget!!

for ev, err := range sse.Read(res.Body, nil) {
    if err != nil {
        // handle read error
        break // can end the loop as Read stops on first error anyway
    }
    // Do something with the events, parse the JSON or whatever.
}

There is a fully working Go program in the LLM example at cmd/llm/main.go.

The mechanism behind the loop is Go 1.23 iterators, officially range-over-func. If you are still on Go 1.22, the README gives the escape hatch: use the GOEXPERIMENT=rangefunc environment variable, for example GOEXPERIMENT=rangefunc go run main.go, or use the iterator without the syntactic sugar.

go
events(func(ev Event) bool {
    // do something with event
    return true // or false to stop iteration
})

That last detail is worth flagging for adoption. The go.mod declares go 1.22, but the idiomatic usage of the client requires 1.23 or an experiment flag, so the effective floor for the pleasant API is higher than the module declares. The library does support the 1.22 path, it just looks different.

The Provider interface, and what it decides

The server side is built around one interface, and understanding it tells you what the library actually does and does not do for you.

go
type Provider interface {
    // Publish a message to all subscribers of the given topics.
    Publish(msg *Message, topics []string) error
    // Add a new subscriber that is unsubscribed when the context is done.
    Subscribe(ctx context.Context, sub Subscription) error
    // Cleanup all resources and stop publishing messages or accepting subscriptions.
    Shutdown(ctx context.Context) error
}

The provider is what dispatches events to clients. When you publish a message, an event, the provider distributes it to all connections, the subscribers. It is the central piece of the server, and the README is explicit that it determines the maximum number of clients your server can handle, the latency between broadcasting events and receiving them client-side, and the maximum message throughput supported by your server.

That is the whole scalability story, and it is delegated rather than solved. Because different use cases have different needs, go-sse allows you to plug in your own system. The examples given are RabbitMQ streams, Redis pub-sub, Apache Kafka, and your own, for example to mock providers in testing.

If an external system is required, an adapter satisfying the Provider interface has to be written, and the Provider documentation sets out the implementation requirements. The README invites you to share those adapters, since go-sse is built with reusability in mind.

But in most cases the power and scalability those external systems bring is not necessary, so the library ships with a default provider built in.

Joe, the default provider, and a server that works from the zero value

Creating a server takes no configuration at all.

go
import "github.com/tmaxmax/go-sse"

s := &sse.Server{} // zero value ready to use!

The zero value is ready to use, which is a small but real design choice. A library whose server type needs a provider wired in before it does anything has a setup cost on every project that might outgrow the default later.

The type also implements the http.Handler interface, and the server is framework-agnostic. The README points at the ServeHTTP implementation to learn how to implement your own custom logic, so you can wrap the SSE handler in whatever your framework expects.

The configuration surface is small.

go
s := &sse.Server{
    Provider: /* what goes here? find out next! */,
    OnSession: /* see Go docs for this one */,
    Logger: /* see Go docs for this one, too */,
}

The provider slot is what you fill in when the built-in one is no longer enough, OnSession is the hook for per-connection lifecycle, and Logger takes the standard library logger.

The default provider has a name, and the README introduces it with some humour: go-sse brings you Joe, the trusty, pure Go pub-sub implementation, who handles all your events by default. Joe is the in-process implementation, and the choice of a person rather than a mechanism is a small signal about how the project presents its own internals.

The repository layout and the release history

The top level of the repository shows a flat library package with the internals kept separate, and the file names map closely onto the concepts.

server.go, client.go, client_connection.go, event.go, message.go and message_fields.go are the public surface. session.go handles the per-connection state. joe.go is the default provider. replay.go is the piece that handles replaying missed events to reconnecting clients, which is the part of server-sent events that most implementations skip and that determines whether a client that drops its connection loses data.

Then there are the test files alongside each, cmd/ for the example programs including the LLM one, internal/ for the private helpers, and .golangci.yml for linting with codecov.yml for coverage reporting.

The project is MIT licensed, the homepage is the pkg.go.dev documentation for the module, and the release history is steady. Version v0.11.0 was released on 2025-05-13, v0.10.0 on 2024-12-28 and v0.9.0 on 2024-12-26, with the last push to the repository on 2026-10-09.

That sequence shows something about the pace. Two releases two days apart in December 2024, then a gap to May 2025, and then commits continuing into the autumn of 2026 without a new tag. For a library at version 0.x with a no-backwards-compatibility promise on master, the tags are the stable points and everything else is moving.

Where go-sse is the wrong choice

Four cases argue against it.

The first is a project on an old Go version. The idiomatic client usage depends on Go 1.23 range-over-func, and the module declares go 1.22, so a team pinned to 1.21 has to either upgrade or use the experiment flag and a less readable form. For a library whose whole selling point is a clean reading loop, that is the wrong trade.

The second is a need for bidirectional communication. Server-sent events are one way. If your application needs the client to send messages over the same connection, you want a websocket, and no amount of provider work changes that.

The third is a deployment that must scale horizontally without thinking about it. The Provider abstraction is the answer, but it is an abstraction you have to fill in. Swap the default in-process provider for Redis pub-sub or Kafka and you have to write and maintain that adapter, and the README's own list of candidate systems is a hint at the operational weight involved.

The alternative in the same direction is a websocket library, or in Go, the standard library's own HTTP plus a small amount of framing code. The difference in approach is that server-sent events are plain HTTP with a text format, so they pass through every proxy, load balancer and corporate proxy that a websocket gets stuck on, and they reconnect automatically with the browser's built-in EventSource. A websocket gives you two-way traffic and lower latency per message at the cost of that infrastructure compatibility, and for a server pushing updates that the client does not need to reply to, SSE is usually the calmer choice.

Editorial conclusion

go-sse fits a Go service that needs to push events to browsers over HTTP without adding a websocket layer, and it fits an LLM SDK that needs to read a streaming API response. It does not fit a codebase still on Go 1.21 or older, since the iterator syntax depends on Go 1.23 range-over-func, and it does not fit a deployment that needs horizontal scaling without thinking about the Provider. Install with `go get -u github.com/tmaxmax/go-sse`, pin a tagged version rather than tracking master, and read the Provider interface before you decide the built-in implementation will carry your load.

Frequently asked questions

What is go-sse?

It is a lightweight, fully spec-compliant HTML5 server-sent events library for Go. It provides both server-side and client-side implementations of the protocol, and the two are decoupled, so you can use the client against any server that emits events.

How do I install go-sse?

With `go get -u github.com/tmaxmax/go-sse`. The README strongly recommends using tagged versions in your projects, because the master branch has tested but unreleased and possibly undocumented changes that may break backwards compatibility.

What is the Provider interface in go-sse?

It is the publish-subscribe messaging system behind the server, with Publish, Subscribe and Shutdown methods. The provider determines the maximum number of clients, the latency between broadcasting and receiving, and the message throughput, and you can plug in RabbitMQ streams, Redis pub-sub, Kafka or your own implementation.

Can I read an LLM streaming response with go-sse?

Yes. Make the HTTP request as usual and call sse.Read on the response body, ranging over the returned iterator. The library notes this is useful if you are implementing an LLM SDK, since it saves you reimplementing event stream parsing, and a full working program is in cmd/llm/main.go.

Which Go version does go-sse need?

The module declares go 1.22 and the library supports the two most recent Go releases, but the idiomatic iterator syntax in the client relies on Go 1.23 range-over-func. On Go 1.22 the README suggests GOEXPERIMENT=rangefunc or using the iterator without the syntactic sugar.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. tmaxmax/go-sse on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tmaxmax-go-sse.svg)](https://hysenlabs.com/projects/tmaxmax-go-sse)

Was this useful?