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
Logger’s response-writer wrapper embeddedhttp.ResponseWriteras an interface, which silently droppedhttp.Hijacker(required for WebSocket upgrades) andhttp.Flusher(required for SSE). Fixed by explicit delegation of both methods.- 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 onegeneratewas originally invoked from. Also fixed a concurrent-write race in the manifest itself, via optimistic concurrency with a version counter, verified clean undergo test -racewith 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.
