Muzak for Go 1.27 · v0.3.1
The signature
is the contract.
Muzak is the type-safe web framework for Go. A handler's input type is the request and its return type is the response, both checked when you compile. Built on net/http, with no third-party dependencies at all.
$ go get muzak.dev/frameworkgo: downloading muzak.dev/framework v0.3.1go: added muzak.dev/framework v0.3.1✓ no third party dependencies$ go run ./cmd14:32:07.482 INFO [Server] Starting Muzak application...14:32:07.514 INFO [Server] Listening on :8080$ ▍
One module, named muzak. Routing, validation, DI and OpenAPI are in it.
Installation
One module, then a running API
Add the module, lay the project out so cmd/main.go does nothing but compose, and run it. Each card below plays a real command and the output Muzak actually prints, from the first request to a graceful shutdown.
$ go versiongo version go1.27.0 darwin/arm64$ go get muzak.dev/framework✓ added muzak.dev/framework v0.3.1
$ tree -L 2 awesome-apiawesome-api/├─ cmd/main.go composition├─ core/ config, dependencies├─ schemas/ request and response models├─ handlers/ the functions that answer└─ routers/ which handler answers which path✓ dependencies point one way
$ go run ./cmd[Server] Starting 2 components in parallel✓ Listening on :8080
$ curl localhost:8080/users/rick{"username":"rick"}200 · the handler's return type, encoded
$ curl 'localhost:8080/users/?limit=nope'422 "limit" must be a valid integerevery offending field, reported at once
$ curl -s localhost:8080/openapi.json | head -3{ "openapi": "3.1.0","info": { "title": "Awesome API"
$ ^C[Server] Shutting down, waiting for in-flight requests...✓ Stopped (drained, then released)
Timeouts are non-zero, bodies are capped, unknown JSON members are rejected, and CORS denies everything until a policy is written. Relaxing any of it is a decision you make out loud.
The contract
Types in, types out
Struct tags say where each field is read from. The return type is the response model, with no wrapper and no filtering pass at run time, so a field you did not declare cannot leak. The same declarations produce the OpenAPI document.
1type Params struct {
2 Username string `path:"username" doc:"The user to look up"`
3}
4
5type UserOut struct {
6 Username string `json:"username"`
7 Email string `json:"email,omitzero"`
8}
9
10r.Get("/users/{username}", func(ctx *muzak.Context, in Params) (UserOut, error) {
11 return UserOut{Username: in.Username}, nil
12}) // type arguments inferred, never writtenv.String(&in.Email).Trim().Lower().Required().Email()
v.Slice(&in.Tags).MaxItems(10).Unique().Each(validate.String().MaxLen(20))
// → "format": "email" · "maxItems": 10 · "uniqueItems": true · items.maxLength: 20
// &in.Email is the field, so renaming it is a change the compiler checksBenchmarks
Compiled once, not per request
Apple M1 Pro, darwin/arm64, Go 1.27, medians of five runs. Every case drives the real code path through App.ServeHTTP, so routing, binding, dependencies and encoding are all included.
FAQ
Questions, answered plainly
The short version of what Muzak is, how a route is written, and what it costs you at runtime.