Important
Aku is an API-first, typesafe Go web framework built on the standard library's net/http, designed for building APIs and web backends.
Aku sits between raw net/http and full-stack application frameworks. It gives you typed request and response contracts, validation, and OpenAPI while remaining a standard http.Handler. You can use its HTTP features as a framework or compose them with ordinary Go handlers, middleware, databases, authentication, templates, and frontend tooling.
- Standard Library First: Built on
net/httpand Go's modernhttp.ServeMux. Purehttp.Handlercompatibility. - Library-like Composition: Aku owns the API boundary without owning your application architecture or infrastructure choices.
- Typesafe Extraction: Automatically map Path, Query, Header, Cookie, Form, and JSON Body into a single request struct.
- Precompiled Extraction Plans: Reflection inspects handler types at registration; request handling reuses the resulting binding plan and coercers.
- Automatic OpenAPI 3.0: Generates documentation, including schemas and security requirements, from your Go types.
- Validation: Support for
go-playground/validatortags (opt in withWithValidator) and explicitValidate() errorhooks. - Streaming & SSE: First-class support for
io.Readerstreaming and Server-Sent Events. - Middleware Suite: Recovery, timeouts, CORS, compression, security headers, rate limiting, circuit breaking, health checks, and OpenTelemetry hooks.
- Integration Testing: The repo's integration suite uses chainable helpers to keep API assertions readable.
Aku is designed with a low-allocation philosophy. By inspecting Go types at route registration time, Aku precomputes extraction steps and coercers for every handler. At runtime, the request path reuses those plans and pooled input values. Field assignment still uses reflect.Value; the optimization is avoiding repeated type inspection and coercer construction, not code-generated zero-reflection execution.
- Pre-compiled Binding: Avoids repeated reflection-based type inspection and conversion setup on each request.
- Buffer & Struct Reuse: Extensive use of
sync.Poolto minimize GC pressure. - Standard Library Speed: Built directly on
http.ServeMuxwith minimal wrapping.
Benchmark results are environment-sensitive; see benchmark_test.go for the comparison suite and rerun it on your target hardware.
package main
import (
"context"
"log"
"net/http"
"github.com/nijaru/aku"
)
type GreetRequest struct {
Path struct {
Name string `path:"name"`
}
Query struct {
Shout bool `query:"shout"`
}
}
type GreetResponse struct {
Message string `json:"message"`
}
func Greet(ctx context.Context, in GreetRequest) (GreetResponse, error) {
msg := "Hello, " + in.Path.Name
if in.Query.Shout {
msg += "!"
}
return GreetResponse{Message: msg}, nil
}
func main() {
app := aku.New()
// Register a route
if err := aku.Get(app, "/greet/{name}", Greet); err != nil {
log.Fatal(err)
}
// Serve OpenAPI UI at /docs
if err := app.OpenAPI("/openapi.json", "My API", "1.0.0"); err != nil {
log.Fatal(err)
}
if err := app.SwaggerUI("/docs", "/openapi.json"); err != nil {
log.Fatal(err)
}
log.Println("Serving on :8080")
log.Fatal(http.ListenAndServe(":8080", app))
}Aku targets the latest stable Go toolchain. The module currently requires Go
1.26.5 so the framework can use current standard-library APIs and runtime
improvements. Some underlying HTTP semantics come from Go 1.22-era ServeMux,
but older toolchains are not a supported build target.
Use the repo-local hook setup to keep Go formatting out of commits:
make hooksThat configures Git to run .githooks/pre-commit, which formats staged Go
files before the commit is created.
Useful local checks:
make fmtto rewrite tracked Go files in placemake fmt-checkto verify formatting without changing filesmake checkto run formatting, tests, and the build
Aku uses struct sections to define where data comes from. Each section is optional.
type CreateProductRequest struct {
Header struct {
IDPToken string `header:"X-IDP-Token"`
}
Path struct {
Category string `path:"category"`
}
Query struct {
Preview bool `query:"preview"`
}
Body struct {
Name string `json:"name" validate:"required"`
Price float64 `json:"price" validate:"gt=0"`
}
}Non-pointer query, header, and form fields are required by default. Add
aku:"optional" when a zero value is meaningful and your handler applies
defaults:
type ListProductsRequest struct {
Query struct {
Offset int `query:"offset" aku:"optional"`
Limit int `query:"limit" aku:"optional"`
}
}Validation tags are enforced when the application is configured with a validator;
explicit Validate() error hooks run without that option:
app := aku.New(aku.WithValidator(validator.New()))Typed JSON bodies default to a 1 MiB framework limit. Set WithMaxBodyBytes to
change or disable that limit; middleware.BodySizeLimit remains available when
the same policy must also cover standard handlers and form uploads. JSON bodies
require application/json or a +json media type, and WithStrictJSON rejects
unknown object fields. If an existing JSON decoder must accept a concrete vendor
media type, add it explicitly with WithJSONRequestMediaTypes; Aku does not
register general request codecs.
Request cookies use a first-class Cookie section:
type Request struct {
Cookie struct {
Session string `cookie:"session"`
}
}Sessions, CSRF, signing, and identity policy remain application or middleware concerns.
Typed responses remain JSON by default. Use an explicit output struct when the
response needs status, headers, cookies, or a non-JSON body. Existing JSON,
text, HTML, bytes, file, stream, and reader serializers can publish a concrete
custom media type with WithResponseMediaType; this changes metadata and the
Content-Type header, not serialization:
type Response struct {
Status int `status:""`
Body aku.HTML `body:""`
}
// A vendor JSON response keeps JSON encoding but publishes the vendor type.
akErr := aku.Get(app, "/report", reportHandler,
aku.WithResponseMediaType("application/vnd.example.report+json"),
)
_ = akErrUse standard func(http.Handler) http.Handler middleware at the application or route level.
app := aku.New(
aku.WithGlobalMiddleware(middleware.Recover, middleware.Logger),
)
aku.Post(app, "/secure", MyHandler,
aku.WithMiddleware(AuthMiddleware),
)Aku makes the common API path conventional without requiring an application to adopt a project layout or an infrastructure stack. Use the first level that matches the route:
- Typed handler: request sections and the output type provide the request, response, validation, and OpenAPI contract.
- Explicit typed response: output fields tagged with
status,header,cookie, orbodymake non-JSON behavior and dynamic metadata local to the response type. - Standard handler:
HandleHTTPandhttp.Handlermiddleware support third-party handlers, custom protocols, and routes that do not benefit from typed extraction. - Additive OpenAPI patch:
WithOpenAPIPatchadds responses, parameters, or tags while retaining the generated operation. Use it for documented application errors such as 403 or 429. - Explicit OpenAPI operation:
WithOpenAPIOperationreplaces the generated documentation for a route when a raw or custom handler's contract cannot be inferred. The registered method and pattern still win, and runtime behavior does not change. Define at least one response in the explicit operation.
For example, a health handler can keep its ordinary net/http implementation
while publishing a precise response contract:
import "github.com/nijaru/aku/openapi"
err := app.HandleHTTP(
http.MethodGet,
"/healthz",
healthHandler,
aku.WithOpenAPIOperation(openapi.Operation{
Summary: "Health status",
Responses: map[string]openapi.Response{
"200": {Description: "The service is healthy."},
},
}),
)The patch and override are documentation escape hatches, not a second runtime contract system. They keep the internal operation descriptor authoritative for dispatch while letting an application describe behavior that Aku cannot discover from a standard handler. Patches compose in option order; an existing response can be enriched with additional media types or headers.
Aku keeps http.Handler compatibility for endpoints that do not need a typed request struct.
HandleHTTP registers any standard handler with route metadata and middleware, while Metrics
is a shorthand for read-only GET endpoints such as /metrics.
if err := app.HandleHTTP(
http.MethodGet,
"/healthz",
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}),
aku.WithSummary("Health check"),
); err != nil {
log.Fatal(err)
}
if err := app.Metrics("/metrics", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})); err != nil {
log.Fatal(err)
}These routes still participate in application, group, and route middleware, and they still feed
OpenAPI metadata. A raw handler can use WithResponseMediaType as a default
header, but its own explicit header remains authoritative.
All registration methods return an error when the input contract, ServeMux pattern, or route
conflicts with an existing registration; check those errors during startup. Static file routes are
intentionally not included in OpenAPI.
app.Run(addr) creates a standard http.Server with bounded defaults:
ReadHeaderTimeout=5s, ReadTimeout=30s, WriteTimeout=30s, and
IdleTimeout=120s. For long-lived streaming endpoints, set the write timeout
to zero or construct your own http.Server with app as the handler.
app := aku.New(aku.WithServerTimeouts(aku.ServerTimeouts{
ReadHeader: 5 * time.Second,
Read: 30 * time.Second,
Write: 0, // allow long-lived streams
Idle: 120 * time.Second,
}))Aku stays API-first. Browser-facing concerns such as cookie sessions, CSRF,
HTML rendering, and template engines should be composed as standard
net/http middleware or http.Handler routes through Use, WithMiddleware,
and HandleHTTP. Aku will not force a session store, CSRF library, template
engine, or application architecture.
For a runnable example that combines typed JSON, multipart upload, webhook input, health probes, an embedded SPA, and generated API docs, run:
go run ./examples/referenceAku's integration tests live in tests/ and use repo-local helpers in
internal/testutil to keep assertions concise.
That helper stays internal for now; the public API is the framework itself, not
another testing abstraction. If you need to write your own Aku tests, use
httptest directly or copy the patterns that fit your app.
MIT