Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

77 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Aku

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.

Features

  • Standard Library First: Built on net/http and Go's modern http.ServeMux. Pure http.Handler compatibility.
  • 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/validator tags (opt in with WithValidator) and explicit Validate() error hooks.
  • Streaming & SSE: First-class support for io.Reader streaming 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.

Performance

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.Pool to minimize GC pressure.
  • Standard Library Speed: Built directly on http.ServeMux with minimal wrapping.

Benchmark results are environment-sensitive; see benchmark_test.go for the comparison suite and rerun it on your target hardware.

Quick Start

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))
}

Development

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 hooks

That configures Git to run .githooks/pre-commit, which formats staged Go files before the commit is created.

Useful local checks:

  • make fmt to rewrite tracked Go files in place
  • make fmt-check to verify formatting without changing files
  • make check to run formatting, tests, and the build

Request Extraction

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"),
)
_ = akErr

Middleware

Use 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),
)

Conventions and Escape Hatches

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:

  1. Typed handler: request sections and the output type provide the request, response, validation, and OpenAPI contract.
  2. Explicit typed response: output fields tagged with status, header, cookie, or body make non-JSON behavior and dynamic metadata local to the response type.
  3. Standard handler: HandleHTTP and http.Handler middleware support third-party handlers, custom protocols, and routes that do not benefit from typed extraction.
  4. Additive OpenAPI patch: WithOpenAPIPatch adds responses, parameters, or tags while retaining the generated operation. Use it for documented application errors such as 403 or 429.
  5. Explicit OpenAPI operation: WithOpenAPIOperation replaces 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.

Standard Handler Escape Hatches

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.

Server Runtime

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,
}))

Browser Applications

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/reference

Testing

Aku'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.

License

MIT

About

Idiomatic Go library for building JSON APIs

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages