GoFast v0.2.0: production auth, real-time, and streaming, still zero runtime reflection

Hi Gophers,

What GoFast is

GoFast is a Go web framework that generates request validation, path/query/multipart binding, and OpenAPI documentation at build time, via go/ast parsing of struct tags — not via runtime reflection. A struct with tags (validate:"required,email", path:"id", query:"page,default=1", form:"file") is parsed once by gofast generate, which writes real, readable .go files (*_validate.gen.go, *_bindpath.gen.go, etc.) committed to the project’s own repository. Handler[In, Out] then calls the generated methods directly at request time — no type inspection, no reflection, per request.

This differs from the existing FastAPI-equivalent in the Go ecosystem, Huma, which achieves the same automatic validation/OpenAPI generation via reflection resolved on every incoming request. GoFast’s original post and full benchmark methodology are there.

This post covers what changed between v0.1.0 and v0.2.0.

Auth

Stateless JWT access/refresh tokens. Revocation via a pluggable TokenRevoker interface, with a production Redis implementation (SETEX-style TTL, no manual cleanup). Revocation fails closed: a Redis error rejects the token rather than allowing it through.

Rate limiting

Same pluggable-interface pattern. In-memory (token bucket, x/time/rate) and Redis (fixed-window counter) implementations. This one fails open — an unreachable limiter degrades to unlimited rather than blocking traffic, a deliberate difference from revocation’s fail-closed behavior, documented in the corresponding ADR.

Lifespan hooks

Router.OnStartup/OnShutdown, and an optional Router.Run built on net/http’s own Shutdown — no custom server implementation.

Real-time

WebSockets via coder/websocket (gorilla/websocket is archived) and Server-Sent Events. Both use a handler type separate from Handler[In, Out], since a long-lived streaming connection does not fit a request/response contract.

Background tasks

SpawnTask, accessed via context injection rather than a signature change to Handler[In, Out]. Coordinated with the shutdown hooks — in-flight tasks are waited on, bounded by a timeout, before process exit.

Multipart/form-data

Implemented via build-time codegen (extending the existing AST-parsing/tag-extraction machinery already used for path:/query:), not runtime reflection. Reflection would have been the simpler implementation but would have contradicted the project’s build-time premise on its own final feature.

Built-in TestClient

Dispatches via httptest.NewRecorder, not a real listener. In-process, no port allocation.

Two bugs found and fixed

  1. Logger’s response-writer wrapper embedded http.ResponseWriter as an interface, which silently dropped http.Hijacker (required for WebSocket upgrades) and http.Flusher (required for SSE). Fixed by explicit delegation of both methods.
  2. The generation manifest (.gofast/manifest.json) never checked whether previously-generated files still existed on disk before skipping regeneration. Fixing that exposed a second bug: generated file paths were recorded as relative rather than absolute, breaking the new existence check when run from a different working directory than the one generate was originally invoked from. Also fixed a concurrent-write race in the manifest itself, via optimistic concurrency with a version counter, verified clean under go test -race with 10 concurrent writers.

Benchmarks

Unchanged core numbers from the original post — build-time validation still ~37.5x faster in isolation, converging toward parity with Huma in a full HTTP cycle once JSON decoding and the Go runtime dominate. Reproduction commands in docs/BENCHMARKS.md.

Status

Pre-v1.0.0. ADR 0005 requires API stability across two consecutive releases before that tag; this is release two. 18 ADRs document the architecture, including explicit limits on what GoFast can claim.

Repo: GitHub - Darkblade1995/gofast: A Go framework that generates request validation, binding, and OpenAPI documentation at build time via go/ast — not through runtime reflection. Benchmarked against Huma: ~37.5x faster isolated validation, ~26% fewer allocations end-to-end. · GitHub