Changelog
All notable changes to this project are documented here. The format follows Keep a Changelog, and the project follows Semantic Versioning.
Until 1.0.0, a minor bump may carry a breaking change. Each one is listed under Changed with the migration.
Unreleased
0.2.0 - 2026-08-24
Changed
- The documentation page is no longer part of the framework. The small
self-contained page that was embedded in the module is gone, and
AppOptions.DocsUInames the UI to serve instead. It is nil by default, so an application publishes its OpenAPI document at/openapi.jsonand serves no page at all unless it asks for one:import "muzak.dev/openapi/ui" app := muzak.New(muzak.AppOptions{DocsUI: ui.Files()})
A UI is a module of its own so that a service which does not want one does not carry it: Go downloads and links a module only when something imports it, so leavingDocsUIunset costs a binary nothing rather than embedding a page it will never serve. Nothing is fetched at run time either way.
Migration: add the import and the option to keep a page at/docs; change nothing to keep only the document.DocsPathis no longer reserved when no UI is configured, so a route of your own may use it.
Added
muzak.dev/openapi/uiserves the OpenAPI dashboard: the reference grouped by tag, every schema as an outline, request snippets in thirteen languages, and a console that sends a request from the page and reports the status, the timing, the headers and the body. It is built from the same document the framework generates, so tags, summaries, descriptions, deprecations, parameters with their validation constraints, request bodies and the response model declared for every status code all reach it.AppOptions.DocsUItakes anyfs.FSmeeting a small contract - anindex.htmlwhose absolute URLs are written under/__muzak_docs__/and which reads its document from/__muzak_spec__- so a service can serve a dashboard of its own instead.
0.1.1 - 2026-08-24
Added
WithResponseModel[T](code, description)documents a status code and the model its body carries, so one operation can describe a different schema per status code rather than the error envelope everywhere: a400and a500carrying the service's own error type, a409carrying a conflict report, a304carrying nothing. The type argument is written exactly as a handler'sOuttype is, and is described once in the components section. LikeWithResponseDocit is a router option as well as a route option, and the last declaration of a status code wins, so a route replaces what it inherited.- An empty description passed to
WithResponseDocorWithResponseModelnow falls back to the status code's standard reason phrase, soWithResponseDoc(404, "")is documented as "Not Found" rather than as a response with no description at all. - A documented response status outside 100-599 is a build error naming the route and the code, rather than a response key in the document that no client could ever receive.
- A request console in the documentation page at
/docs. Every operation can be sent from the page itself, with the parameters, the JSON body and the multipart form filled in beside the schema they come from; the response is shown with its status, timing, size, headers and body, an event stream is read as it arrives, and the same request can be copied as acurlcommand. An Authorize panel adds a bearer token, an API key header or basic credentials to what the console sends. They are held in the tab and never stored. - The rest of the page grew with it: operations grouped by tag with a description per group, schemas as expandable outlines carrying the constraints the application enforces, generated examples, a filter over every operation, deep links to an operation or a group, and a light, dark or system theme.
- A constructor for each HTTP outcome worth a name:
muzak.NotFound(message),muzak.Forbidden(message),muzak.Conflict(message)and seventeen more, covering 400 through 504. Each returns an*HTTPErrorcarrying that status and its classifier, soWrap,WithCodeandWithDetailschain onto every one of them, and an empty message uses the standard sentence for the status.NewHTTPError(status, message)still covers anything without a name of its own. - Machine-readable codes for the statuses that had none:
payment_required,not_acceptable,request_timeout,gone,precondition_failed,not_implemented,bad_gateway,service_unavailableandgateway_timeout. - The address the documentation ended up at is reported when the server starts
listening, as a URL that can be opened from the terminal:
Documentation at http://localhost:8080/docs. A wildcard bind is reported as localhost, since that is where a browser can reach it. AppOptions.DocsPathandAppOptions.OpenAPIPathare now validated while the application is built. A path that is not absolute, one that is the same as the other, or one that an application route already answers is a build error naming the option to change, rather than a page nobody can reach or a route silently shadowed by the documentation.OpenAPIOptions.Tagsdescribes the groups operations are sorted into and decides the order the documentation presents them in. Routes join a group withWithTagsas before; a described tag no route carries is left out, and a tag nothing describes follows the described ones.
Changed
CodeForStatusreturns a specific classifier for the nine statuses listed above instead of the genericclient_errororinternal_error. A client switching on the code sees the more precise value; one switching on the status is unaffected.- The documentation page and the OpenAPI document are compressed once when the application is built and served with an entity tag per representation, so a client that accepts gzip transfers a fraction of the bytes and a reload transfers none.
- The page's content security policy now names the page's own script and
stylesheet by hash instead of by a nonce issued per response. The page is a
constant again, which is what lets it be cached, revalidated and compressed
ahead of time;
Cache-Controlisno-cacherather thanno-store.
0.1.0 - 2026-08-23
First public release, published as muzak.dev/framework on the Go module proxy.
This is a pre-1.0 version. The surface is covered by tests and used by the example application, but it is not frozen: expect it to move before 1.0.0.
Added
- Routing on a segment-wise radix trie, with static and parameter segments and routers that nest under a prefix.
- Handlers of the form
func(ctx *muzak.Context, in In) (Out, error), where the input type is the request and the return type is the response. Neither type argument is written at the call site. - Request binding by struct tag from
path,query,header,cookie,formandfile, or from the JSON body when no tag is present, with the binding plan compiled once per route. - Validation declared against the field address rather than its name, so
v.String(&in.Email)survives a rename andv.Number(&in.Email)does not compile. - Dependencies: guards through
WithDependenciesand typed providers read back withFrom[T](ctx), plusWithSingletonfor values built once. - OpenAPI 3.1 generated from the same declarations the code runs on, served at
/openapi.jsonwith a self-contained UI at/docs. - RFC 6455 WebSockets and typed server-sent events, implemented in the module rather than delegated to a dependency.
- Rate limiting, configuration loading through
MustLoadConfig, and an in-process test client undertestclient. - API versioning.
AppOptions.Versioningturns it on,WithVersiondeclares what a route or router answers, and the version is read from the path, a header, theAcceptheader or a function of your own. See Versioning. - Per-address bounds on long-lived connections:
WSOptions.MaxConnectionsPerIPandSSEOptions.MaxStreamsPerIP, so one client cannot hold every slot the process has. - Conservative defaults throughout: non-zero listener timeouts, a one mebibyte body cap, rejection of unknown JSON members, CORS closed until a policy is written, cross-origin WebSocket handshakes refused, no forwarding header believed until a proxy is named, and a panic reported as a generic 500 with the stack kept in the log. The full list is in Safe Defaults.
- Dual licence, MIT or Apache-2.0 at your option.