On this page

Routing

A route connects an HTTP method and path to a typed Go handler. Register it with huma.Register: the same declaration supplies routing, input validation, and OpenAPI. Modules receive ready-to-use APIs through dependencies.Targets.

Start with your first module if you have not added one yet. This page explains how its HTTP routes fit together; handlers covers the code that runs after a request has been validated.

Register a route#

Inside the module's Register function:

if targets.HTTP != nil {
    huma.Register(targets.HTTP, huma.Operation{
        OperationID: "catalog.greet",
        Method:      "GET",
        Path:        "/catalog/hello/{name}",
        Summary:     "Greet a visitor",
    }, func(ctx context.Context, input *GreetingRequest) (*response.JSON[GreetingResponse], error) {
        return response.OK(GreetingResponse{Message: "Hello, " + input.Name}), nil
    })
}

Imports: context, github.com/danielgtaylor/huma/v2, and github.com/wisalgroup/labena/internal/platform/api/response. The request and response types are:

type GreetingRequest struct {
    Name string `path:"name" minLength:"1"`
}

type GreetingResponse struct {
    Message string `json:"message"`
}

App supplies a group prefixed with /api/v1, so this operation is available at:

curl http://localhost:8080/api/v1/catalog/hello/Ada
{
  "data": {"message": "Hello, Ada"}
}

This is an alternative registration for the existing Catalog example; do not register both versions at the same path. The complete compiling starter lives in examples/module/catalog/register.go.

A nil HTTP target means that transport is omitted. Skip its registration and continue registering other module capabilities. Registration installs definitions; it starts no listeners, goroutines, or business work.

Choose a public or protected route#

Target Use it for What it supplies
targets.HTTP Public operations such as requesting a sign-in code The business API prefix and common policies
targets.AuthenticatedHTTP Operations requiring a signed-in user The same prefix plus bearer enforcement and OpenAPI security

Use the protected target for account-specific routes:

if targets.AuthenticatedHTTP != nil {
    registerAccountRoutes(targets.AuthenticatedHTTP, users)
}

Here registerAccountRoutes is a module-owned helper accepting huma.API and its existing service contract. It receives the service directly; it does not look up application resources during a request.

Authentication proves who is calling. The handler or service must still authorize the requested resource. See handler authorization and Identity.

Group routes and policies#

Create a group for a shared prefix, tag, or policy. This example belongs in a registration helper after checking the target is non-nil:

catalog := huma.NewGroup(targets.HTTP, "/catalog")
catalog.UseSimpleModifier(func(op *huma.Operation) {
    op.Tags = []string{"catalog"}
})

huma.Register(catalog, huma.Operation{
    OperationID: "catalog.listProducts",
    Method:      "GET",
    Path:        "/products",
    Summary:     "List products",
}, handler.list)

handler.list is the typed handler you implement for that operation. Its effective path is /api/v1/catalog/products. Do not repeat /api/v1 in the module.

Install group middleware and modifiers before registering operations. Prefer a module-owned child group when changing tags or policies, so unrelated modules do not inherit them. Use huma.Register and huma.Operation directly; there is no second framework registration API.

Describe an operation#

The most useful operation fields are:

Field When to set it
OperationID A stable, unique name for generated clients and route identification
Method, Path The HTTP method and resource path
Summary, Description, Tags Short purpose, additional contract details, and documentation grouping
DefaultStatus Fixed successful status other than the inferred default
Errors Failure statuses you want explicitly listed in OpenAPI
MaxBodyBytes, BodyReadTimeout Body size and read deadline appropriate to this operation
Middlewares A policy belonging only to this operation

For example, registering a create handler:

huma.Register(catalog, huma.Operation{
    OperationID:     "catalog.createProduct",
    Method:          "POST",
    Path:            "/products",
    Summary:         "Create a product",
    DefaultStatus:   201,
    Errors:          []int{401, 409, 422},
    MaxBodyBytes:    4096,
    BodyReadTimeout: 5 * time.Second,
}, handler.create)

Import time for this fragment. The handler returns response.Created(result). The status constructor controls the actual response; DefaultStatus describes it in OpenAPI. For operations returning more than one successful status, see responses.

Operation IDs are derived when omitted. Use explicit IDs for a public contract and keep them stable when only documentation wording changes. Huma registration and ServeMux pattern conflicts are programmer errors; validate routes in tests before serving.

Path parameters and query values#

Use {name} for a path parameter and declare its source on the input:

type ProductRequest struct {
    ID     string `path:"id" minLength:"1" maxLength:"200"`
    Locale string `query:"locale" default:"en" enum:"en,ar"`
}

Register /products/{id}. The handler reads input.ID and input.Locale after binding and validation; it does not parse the URL again. Add body or header fields to the same input when needed. See requests.

Trailing slashes#

Register resource paths without a trailing slash. An unregistered trailing-slash variant such as /api/v1/auth/otp/send/ redirects to /api/v1/auth/otp/send with 308, preserving the method and body. Query strings and escaped path parameters are preserved. Use the canonical path to avoid a round trip.

/docs/ redirects to /docs, keeping its relative OpenAPI URL correct. Explicit trailing-slash and subtree registrations retain ServeMux semantics. Unknown paths remain 404. Repeated separators and dot segments follow Go's standard path cleaning; do not use them as alternate resource URLs.

Server setup and infrastructure#

api.New(api.Options{}) constructs a Huma API backed by http.ServeMux. App creates the /api/v1 group and supplies it to modules. HTTP_ADDRESS selects the listener. App owns serving, readiness, and shutdown.

Route Purpose
GET /healthz Liveness
GET /readyz Startup, dependency, and shutdown readiness
/api/v1/* Typed business operations
GET /docs Live interactive API reference
GET /openapi.json OpenAPI from registered operations

Infrastructure handlers mount on server.Mux using standard http.Handler APIs. Serve server so correlation, logging, recovery, and observability also cover those handlers. Serving server.Mux directly bypasses the surrounding policies. Health, metrics, dashboards, and standard 404/405 responses retain their own formats.

For use without full app assembly, construct an API server directly and supply it to a standard HTTP server or pkg/httpserver. Finish registration before accepting requests.

Inspect and verify routes#

go run ./cmd/cli route list
curl --fail http://localhost:8080/openapi.json

The CLI constructs the application and its configured connections; it does not start workers merely to inspect routes. The live specification reflects the running instance, so restart after Go code changes. See API reference.

Symptom Check
404 after adding a route The module Register function is called; the HTTP target is non-nil; the prefix is correct
405 The path exists but the HTTP method does not match
401 The operation uses the protected target and needs a valid bearer token
New route absent from /docs Restart the running binary and confirm its feature settings
Duplicate registration panic One method/path is being registered more than once or patterns conflict

Continue with handlers, request validation, and middleware.

Documentation

Search guides, sections, and API names.