Introduction
Muzak is a web framework for Go. You use it to build HTTP APIs: a function becomes a route, the input type of that function is the request, and the return type is the response body. Both are checked when you compile, not when a request arrives.
The framework is built on net/http and Go 1.27, with no third-party dependencies at
all. The pieces you reach for every day (routing, request binding, validation,
dependency injection, configuration, logging, OpenAPI) are part of the framework, not
separate modules you have to wire together yourself.
Here is a complete application. It answers GET /users/{username} with a JSON object.
package main
import (
"log"
"muzak.dev/framework"
)
type Params struct {
Username string `path:"username" doc:"The username to look up"`
}
type UserOut struct {
Username string `json:"username"`
}
func main() {
app := muzak.New(muzak.AppOptions{Title: "Hello", Version: "1.0.0"})
app.Get("/users/{username}", func(ctx *muzak.Context, in Params) (UserOut, error) {
return UserOut{Username: in.Username}, nil
})
log.Fatal(app.RunSignals())
}
Params is the request. UserOut is the response. You never write the type arguments
for app.Get: Go 1.27 added generic methods and generalized function type inference, so
the router reads both types off the handler literal.
What you get
- Routing from typed functions. A handler is
func(ctx *muzak.Context, in In) (Out, error). There is no wrapper type around the response and no filtering pass at run time, so a field you did not declare onOutcannot leak. - Typed request data. Struct tags say where each field comes from:
path,query,header,cookie,form,file, or the JSON body when a field carries none of them. Bad input is rejected with422before your function runs. - Validation against the field itself.
v.String(&in.Email).Trim().Lower().Required().Email()names the field by address, so renaming it is a change the compiler checks and there is no tag string to typo. - Dependency injection in two shapes. A guard validates and produces nothing; a
provider produces a typed value that the handler reads with
muzak.From[T](ctx). - Nested routers. A package exports its own router and stays unaware of the prefix, tags and guards it will eventually run under. The application decides where things mount.
- OpenAPI for free. Routes, models and validation rules produce an OpenAPI 3.1
document at
/openapi.jsonand a self-contained documentation page at/docs. - API versioning. A route declares the version or versions it answers, read from the
path, a header, the
Acceptheader or a function of your own. - Real-time built in.
Router.WSspeaks RFC 6455 WebSockets andRouter.SSEserves typed server-sent events, both behind the same middleware, guards and binding as any other route. - Safe defaults. Every listener timeout is non-zero, bodies are capped, unknown JSON members are rejected, CORS denies everything until a policy is written, and a panic becomes a generic 500 with the stack in the log rather than in the response.
What a bigger application looks like
Each router is written on its own. The application decides where they mount and what protects them, and that decision lives in one visible place.
settings := muzak.MustLoadConfig[core.Settings](muzak.EnvFile(".env"))
app := muzak.New(muzak.AppOptions{
Title: "Awesome API",
Version: "1.0.0",
Addr: settings.Addr,
},
muzak.WithDependencies(core.GetQueryToken),
muzak.WithSingleton(settings),
)
app.Include(routers.Users())
app.Include(routers.Items())
app.Include(routers.Admin(),
muzak.WithPrefix("/admin"),
muzak.WithTags("admin"),
muzak.WithDependencies(core.GetTokenHeader(settings)),
)
log.Fatal(app.RunSignals())
App embeds *Router, so app.Get(...) works at the root using the same generic
methods any nested router uses.
Where to go next
Start with First Steps to create a project and run it. After that, Routers covers paths and methods, Request Data covers how the input struct is filled in, and Dependencies shows how services and resources reach your handlers.