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:

  1. Reads the scope from the request context (godi.FromContext(ctx)).

  2. Resolves the controller C from that scope.

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