# coder/websocket: a minimal Go WebSocket library with context.Context at its core

> The coder/websocket library gives Go developers a small, idiomatic WebSocket API with first-class context support and zero dependencies. It is a good fit for servers and clients written in idiomatic Go, and a weaker fit if you need prepared writes or configurable buffer sizes.

**coder/websocket** — Minimal and idiomatic WebSocket library for Go

- Repository: https://github.com/coder/websocket
- Stars: 5,490 · Forks: 379
- Language: Go
- License: ISC
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/coder-websocket

## What coder/websocket solves and who should reach for it

Go has no WebSocket implementation in the standard library, and the two obvious historical choices are awkward: golang.org/x/net/websocket is deprecated, and gorilla/websocket predates the context package. coder/websocket fills that gap with an API that looks like the rest of net/http. The README describes it as "minimal and idiomatic", and the repository layout backs that up: accept.go, dial.go, read.go, write.go, close.go, compress.go and netconn.go, with a wsjson subpackage for JSON payloads.

The intended audience is Go engineers writing servers or clients who want WebSocket handling to behave like ordinary HTTP handling. Contexts flow through Accept, Dial, Read and Write, so cancellation and deadlines work the way they do elsewhere in a Go service. The project also compiles to Wasm, which matters if the same code needs to run in a browser. If your stack is Python, Node.js, Java or Spring Boot, this library is not for you. It is a Go package, not a protocol or a service.

## How the API works: contexts, frames and the wsjson subpackage

The mechanism is a thin layer over net/http on the server side and net/http.Client on the client side. websocket.Accept upgrades an http.ResponseWriter, and websocket.Dial opens a connection, with the README noting that Dial uses net/http.Client, which the project says will enable easy HTTP/2 support in the future. HTTP/2 is still a roadmap item, so today the transport is the HTTP/1.1 upgrade path.

After the handshake, reads and writes take a context. The README's server example explicitly warns against using r.Context(), because of surprising behavior related to http.Hijacker, and instead builds a fresh context with a timeout. That is a real design constraint worth internalising: the request context is not the connection context. Frames are handled internally in frame.go and mask.go, with assembly implementations in mask_amd64.s and mask_arm64.s plus a pure-Go fallback in mask_go.go. The wsjson subpackage wraps Read and Write for JSON payloads and, per the README, provides transparent message buffer reuse.

The highlights list is specific: zero dependencies, zero alloc reads and writes, concurrent writes, a close handshake, a net.Conn wrapper, a ping pong API, RFC 7692 permessage-deflate compression, a CloseRead helper for write-only connections, and full Autobahn test suite compliance. Those are claims from the README, not measurements I have reproduced.

## Installing coder/websocket and writing a first server and client

The README gives a single install command. It requires Go 1.23 according to go.mod, so check your toolchain before starting.

```bash
go get github.com/coder/websocket
```

A minimal server accepts the upgrade and reads one JSON value. Note the comment in the README about not using r.Context() directly.

```go
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	c, err := websocket.Accept(w, r, nil)
	if err != nil {
		// ...
	}
	defer c.CloseNow()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()

	var v any
	err = wsjson.Read(ctx, c, &v)
	if err != nil {
		// ...
	}

	log.Printf("received: %v", v)

	c.Close(websocket.StatusNormalClosure, "")
})
```

The client side mirrors it. Dial, write with wsjson, then close with a status code.

```go
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

c, _, err := websocket.Dial(ctx, "ws://localhost:8080", nil)
if err != nil {
	// ...
}
defer c.CloseNow()

err = wsjson.Write(ctx, c, "hi")
if err != nil {
	// ...
}

c.Close(websocket.StatusNormalClosure, "")
```

After running both, the server should log the value the client sent and then close with StatusNormalClosure. The repository's Makefile exposes fmt, lint, test and bench targets, all of which delegate to scripts under ci/.

## Where coder/websocket is the wrong tool

The README is unusually candid in its comparison section, and the advantages it credits to gorilla/websocket are the limitations to weigh. Gorilla offers prepared writes, so you can build a message once and send it to many connections without re-encoding. It also offers configurable buffer sizes. coder/websocket lists neither, so workloads dominated by broadcasting the same payload to thousands of peers will find the API less convenient.

The second limitation is architectural. gobwas/ws and lesismal/nbio expose event-driven APIs for performance-sensitive workloads, and the README acknowledges that flexibility while calling both "quite bloated". If your design is a single goroutine reading from an event loop rather than a goroutine per connection, the idiomatic blocking model here fights you. The README's own framing is that "when writing idiomatic Go, github.com/coder/websocket will be faster and easier to use", which is a statement about a programming style, not a universal performance claim.

Several features are still roadmap items rather than shipped code: graceful shutdown helpers, a ping pong heartbeat helper, instrumentation callbacks, wstest.Pipe for in-memory testing, and HTTP/2. The README does not document rollback behavior or migration guidance from gorilla, so plan that work yourself.

## gorilla/websocket and gobwas/ws: the real differences in approach

gorilla/websocket is the mature incumbent. It writes directly to a net.Conn, which means it duplicates features of net/http.Client but also gives it fine-grained control. coder/websocket instead dials through net/http.Client, so proxies, TLS configuration and transport settings come from the standard library. That is the core architectural difference: one owns the socket, the other delegates to net/http.

The API surface differs in two practical ways. Gorilla requires registering a pong callback before sending a Ping; coder/websocket exposes a ping pong API directly on the connection. Gorilla supports permessage-deflate only in no-context-takeover mode, while coder/websocket claims full RFC 7692 support. coder/websocket also documents a close handshake and a CloseRead helper for write-only connections, both of which the README links to open gorilla issues for.

gobwas/ws sits at the opposite end. Its event-driven style suits million-connection designs, and the README points to the author's blog post on that topic. The trade-off is a much larger API surface. If your codebase is ordinary Go with goroutines and contexts, coder/websocket is the smaller dependency. If you are optimising for connection count per core, the event-driven libraries are built for that.

## Maintenance, licensing and the cost of upgrading

The repository is not archived and the last push was on 2026-06-15, which is the same date as the v1.8.15 release. The release cadence is visible in the tags: v1.8.13 in March 2025, v1.8.14 in September 2025, v1.8.15 in June 2026. That is a slow, low-frequency release rhythm, which is consistent with a small library that has already passed the Autobahn suite and has few moving parts. The README notes that Coder took over maintenance from nhooyr, who authored and maintained the project from 2019 to 2024.

The licence is ISC, a permissive licence similar in effect to MIT. It permits use, modification and redistribution provided the copyright notice and permission notice are retained. As with any dependency, the obligation to include the notice in distributed binaries or source is a question for your own legal review, not something this article can settle.

Upgrade cost is low by design: go.mod declares go 1.23 and the README states zero dependencies, so a version bump pulls in no transitive modules. The Makefile targets fmt, lint, test and bench run through ci/ scripts, so you can reproduce the project's own checks locally before upgrading.

## Conclusion

Adopt coder/websocket if your service is written in idiomatic Go and you want context-aware reads and writes without pulling in dependencies. Do not adopt it if you depend on gorilla/websocket's prepared writes or configurable buffer sizes, or if you need an event-driven API for very high connection counts. Before committing, verify that the close-handshake and ping-pong behaviour in the echo example match your shutdown path, and check whether the roadmap items you care about, such as HTTP/2 or graceful shutdown helpers, have landed.

## FAQ

### How do I install coder/websocket in a Go project?

Run go get github.com/coder/websocket from your module. The README gives that as the only install step, and go.mod requires Go 1.23.

### Does coder/websocket support context cancellation?

Yes. Context support is listed as a highlight, and Accept, Dial, Read and Write all take a context. The README recommends building your own context with a timeout rather than using r.Context() directly.

### Is coder/websocket better than gorilla/websocket?

It depends on what you need. The README credits gorilla with prepared writes and configurable buffer sizes, while claiming a smaller API, zero alloc reads and writes, concurrent writes, a close handshake and full permessage-deflate support for coder/websocket.

### How do I send JSON over coder/websocket?

Use the wsjson subpackage. The README's examples call wsjson.Read(ctx, c, &v) and wsjson.Write(ctx, c, "hi"), and the subpackage is described as providing transparent message buffer reuse.

### Does coder/websocket support HTTP/2?

Not yet. HTTP/2 is listed as an open roadmap item, though the README notes that Dial uses net/http.Client, which it says will enable easy HTTP/2 support in the future.

### Can coder/websocket run in the browser via Wasm?

Yes. Compiling to Wasm is listed in the highlights, and the repository contains ws_js.go and netconn_js.go alongside the non-JS variants.

## Sources

- [coder/websocket on GitHub](https://github.com/coder/websocket)
- [Issues](https://github.com/coder/websocket/issues)
- [License: ISC](https://github.com/coder/websocket/blob/master/LICENSE)
- [README](https://github.com/coder/websocket/blob/master/README.md)
- [Releases](https://github.com/coder/websocket/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/coder-websocket
