--- name: huma description: Huma - Go REST/RPC API Framework. Use with Echo adapter (humaecho) for OpenAPI 3.1 generation. version: 1.0.0 user-invocable: false argument-hint: "" --- # Huma - Go REST/RPC API Framework Huma generates OpenAPI 3.1 from Go types with zero annotations beyond struct tags. Use the Echo adapter (`humaecho`) for this project. ## Core Pattern ```go import ( "github.com/danielgtaylor/huma/v2" "github.com/danielgtaylor/huma/v2/adapters/humaecho" ) // Setup in main.go apiGroup := e.Group("/api/v1") api := humaecho.NewWithGroup(e, apiGroup, huma.DefaultConfig("API", "1.0.0")) // Handler registration - auto-generates OpenAPI huma.Register(api, huma.Operation{ OperationID: "get-user", Method: http.MethodGet, Path: "/users/{id}", Summary: "Get a user", Tags: []string{"Users"}, Errors: []int{404}, }, func(ctx context.Context, input *GetUserInput) (*GetUserOutput, error) { // handler logic }) ``` ## Input/Output Structs Input structs use tags for path, query, header, body parameters. Output structs define response shape. Always wrap outputs in explicit `Body` field to avoid Huma interpreting fields as headers. ```go type GetUserInput struct { ID string `path:"id" doc:"User ID"` Verbose bool `query:"verbose" doc:"Include extra details"` // Body is optional for GET } type GetUserOutput struct { Body struct { ID string `json:"id"` Name string `json:"name"` } } ``` **IMPORTANT**: Fields named `Status` on output structs must use `Body` wrapper, otherwise Huma interprets them as HTTP status codes (must be int). Same for `CreatedAt` which gets treated as response headers. ## Validation Tags ```go type CreateInput struct { Body struct { Name string `json:"name" minLength:"1" maxLength:"100"` Email string `json:"email" format:"email"` Age int `json:"age" minimum:"0" maximum:"150"` Role string `json:"role" enum:"admin,user,guest"` Tags []string `json:"tags" minItems:"1" uniqueItems:"true"` } } ``` ## Error Handling ```go return nil, huma.Error404NotFound("not found") return nil, huma.Error400BadRequest("bad request", &huma.ErrorDetail{...}) return nil, huma.Error401Unauthorized("unauthorized") return nil, huma.Error500InternalServerError("internal error") ``` ## Middleware Huma middleware signature: `func(ctx huma.Context, next func(huma.Context))` ```go // Per-operation middleware huma.Register(api, huma.Operation{ Middlewares: huma.Middlewares{authMiddleware}, }, handler) // Global middleware api.UseMiddleware(loggingMiddleware) // Context values ctx = huma.WithValue(ctx, key, value) val := ctx.Context().Value(key) ``` ## Echo Adapter Notes - `humaecho.New(echo, config)` - creates API from Echo instance - `humaecho.NewWithGroup(echo, group, config)` - creates API for a group path - Path conversion: Huma `{param}` → Echo `:param` (automatic) - Error writing requires API reference: pass `api` to middleware factory ## OpenAPI Spec The spec is auto-generated. Access via `api.OpenAPI()` and serve as JSON: ```go e.GET("/openapi.json", func(c echo.Context) error { data, _ := json.Marshal(api.OpenAPI()) return c.Blob(http.StatusOK, "application/json", data) }) ``` ## Convenience Methods ```go huma.Get(api, "/items", handler) // auto-generates operation ID huma.Post(api, "/items", handler) huma.Put(api, "/items/{id}", handler) huma.Delete(api, "/items/{id}", handler) ``` ## Gotchas 1. Don't return `*models.Model` directly if it has fields named `Status`, `CreatedAt` etc. — use explicit output structs with `Body` wrapper 2. Handler methods should take `api huma.API` and call `huma.Register` internally (method-as-registrar pattern) 3. `huma.WriteErr(api, ctx, status, msg)` needs the API reference — pass it via closure in middleware factories 4. `huma.WithValue` takes 3 args: `(ctx, key, value)`, not `(ctx, context.Context)`