Skip to content

BYOR (Bring Your Own Router)#

Huma is designed to be router-agnostic to enable incremental adoption in existing and new services across a large number of organizations. This means you can use any router you want, or even write your own. The only requirement is an implementation of a small huma.Adapter interface. This is how Huma integrates with your router.

Adapters are in the adapters directory and named after the router they support. Many common routers are supported out of the box (in alphabetical order):

New Adapters

Writing your own adapter is quick and simple, and PRs are accepted for additional adapters to be built-in.

Chi Example#

Adapters are instantiated by wrapping your router and providing a Huma configuration object which describes the API. Here is a simple example using Chi:

main.go
import (
	"github.com/danielgtaylor/huma/v2"
	"github.com/danielgtaylor/huma/v2/adapters/humachi"
	"github.com/go-chi/chi/v5"
)

// Create your router.
router := chi.NewMux()

// Wrap the router with Huma to create an API instance.
api := humachi.New(router, huma.DefaultConfig("My API", "1.0.0"))

// Register your operations with the API.
// ...

// Start the server!
http.ListenAndServe(":8888", r)

For existing services using Chi v4, you can use humachi.NewV4 instead.

Route Groups & Base URLs#

Many routers support grouping of operations under a common base URL path. This is useful for versioning APIs or grouping related operations together. Huma's router adapters can be instantiated using these route groups. You can set the OpenAPI().Servers slice to include the base URL path for the group, enabling the correct URLs to be generated for the docs & schemas. Here is an example:

main.go
mux := chi.NewMux()
mux.Route("/api", func(r chi.Router) {
	config := huma.DefaultConfig("My API", "1.0.0")
	config.Servers = []*huma.Server{
		{URL: "https://example.com/api"},
	}
	api = humachi.New(r, config)

	// Register operations...
	huma.Get(api, "/demo", func(ctx context.Context, input *struct{}) (*struct{}, error) {
		// TODO: Implement me!
		return nil, nil
	})
})
http.ListenAndServe("localhost:8888", mux)

The OpenAPI, docs, schemas, etc will all be generated with the /api prefix.

Shell
# Call the demo operation
restish :8888/api/demo

# Get the OpenAPI
restish :8888/api/openapi.yaml

The following scenarios are supported:

Server Base URL Route Group Register Path Client Request & Description
- - /api/demo GET /api/demo
The default simple case.
/api - /demo GET /api/demoGET /demo
E.g. an API gateway which forwards requests to the service after stripping the /api prefix off the path.
/api /api /demo GET /api/demo
Unmodified request with route groups.

Dive Deeper#

The adapter converts a router-specific request context like http.Request or fiber.Ctx into the router-agnostic huma.Context, which is then used to call your operation's handler function.

graph LR
	Request([Request])
	OperationHandler[Operation Handler]

	Request --> Router
	Router -->|http.Request\nfiber.Ctx\netc| huma.Adapter
	subgraph huma.API
		huma.Adapter -->|huma.Context| OperationHandler
	end