Huma Integration
Guide for using godi with the Huma REST API framework.
Huma is router-agnostic: it runs on top of an adapter (humago, humachi,
humagin, humaecho, humafiber) and propagates the underlying request’s
context.Context to your operation handlers. godi builds on that — the request
scope is created by the router’s ScopeMiddleware, and Huma carries it
through, so godi.FromContext works inside Huma handlers.
So the godi/v5/huma package provides only the Huma-specific piece: a type-safe
controller wrapper for huma.Register. Scope creation comes from the router
integration (godigin, godichi, godihttp, godiecho, godifiber).
Installation
go get github.com/junioryono/godi/v5
go get github.com/junioryono/godi/huma/v5
# plus the router integration you mount Huma on, e.g.:
go get github.com/junioryono/godi/gin/v5
Quick Start
package main
import (
"context"
"net/http"
"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/adapters/humagin"
"github.com/gin-gonic/gin"
"github.com/junioryono/godi/v5"
godigin "github.com/junioryono/godi/gin/v5"
godihuma "github.com/junioryono/godi/huma/v5"
)
type GreetInput struct {
Name string `path:"name"`
}
type GreetOutput struct {
Body struct {
Message string `json:"message"`
}
}
type UserController struct{}
func NewUserController() *UserController { return &UserController{} }
func (c *UserController) Greet(ctx context.Context, in *GreetInput) (*GreetOutput, error) {
out := &GreetOutput{}
out.Body.Message = "hello " + in.Name
return out, nil
}
func main() {
services := godi.NewCollection()
services.AddScoped(NewUserController)
provider, _ := services.Build()
defer provider.Close()
g := gin.New()
// 1. The router middleware owns the request scope.
g.Use(godigin.ScopeMiddleware(provider))
// 2. Mount Huma on the router.
api := humagin.New(g, huma.DefaultConfig("My API", "1.0.0"))
// 3. Register operations against DI-resolved controllers.
huma.Register(api, huma.Operation{
OperationID: "greet",
Method: http.MethodGet,
Path: "/greet/{name}",
}, godihuma.Handle((*UserController).Greet))
g.Run(":8080")
}
How Handle works
godihuma.Handle adapts a controller method to Huma’s registration signature.
Pass the method as a method expression so the receiver becomes the first
parameter:
huma.Register(api, op, godihuma.Handle((*UserController).Greet))
For each request it:
Reads the scope from the request context (
godi.FromContext(ctx)).Resolves the controller
Cfrom that scope.Calls
method(controller, ctx, in).
A failure to resolve the scope or controller returns a 500 by default; the
controller’s returned error is passed through unchanged (Huma renders
huma.StatusError values directly).
Resolving services directly
Inside a handler the ctx argument is the request context, and inside Huma
middleware you have humaCtx.Context(). Use the standard godi helpers — there
is no Huma-specific accessor:
func (c *UserController) Greet(ctx context.Context, in *GreetInput) (*GreetOutput, error) {
scope, err := godi.FromContext(ctx)
if err != nil {
return nil, err
}
svc, err := godi.Resolve[*UserService](scope)
// ...
}
Mapping domain errors
To translate domain errors into HTTP responses for every wrapped handler, pass
WithErrorMapper:
mapErr := func(err error) error {
if errors.Is(err, sql.ErrNoRows) {
return huma.Error404NotFound("not found")
}
return err
}
huma.Register(api, op, godihuma.Handle((*UserController).Greet, godihuma.WithErrorMapper(mapErr)))
Huma-level middleware
Auth, logging, and other per-operation middleware use Huma’s native
api.UseMiddleware — godi does not wrap it. The scope is already on the
context, so middleware can resolve services too:
api.UseMiddleware(func(ctx huma.Context, next func(huma.Context)) {
scope, err := godi.FromContext(ctx.Context())
if err != nil {
huma.WriteErr(api, ctx, http.StatusInternalServerError, "internal error", err)
return
}
// ... inspect/resolve services, then continue or short-circuit
next(ctx)
})