huma Documentation

Huma REST/HTTP API Framework for Golang with OpenAPI 3.1

RAW Doc

File: README.md

πŸŒŽδΈ­ζ–‡ζ–‡ζ‘£
πŸ‡―πŸ‡΅ζ—₯本θͺžγƒ‰γ‚­γƒ₯γƒ‘γƒ³γƒˆ


A modern, simple, fast & flexible micro framework for building HTTP REST/RPC APIs in Go backed by OpenAPI 3 and JSON Schema. Pronounced IPA: /'hjuːmΙ‘/. The goals of this project are to provide:

  • Incremental adoption for teams with existing services
    • Bring your own router (including Go 1.22+), middleware, and logging/metrics
    • Extensible OpenAPI & JSON Schema layer to document existing routes
  • A modern REST or HTTP RPC API backend framework for Go developers
  • Guard rails to prevent common mistakes
  • Documentation that can't get out of date
  • High-quality generated developer tooling

Features include:

  • Declarative interface on top of your router of choice:
    • Operation & model documentation
    • Request params (path, query, header, or cookie)
    • Request body
    • Responses (including errors)
    • Response headers
  • JSON Errors using RFC9457 and application/problem+json by default (but can be changed)
  • Per-operation request size limits with sane defaults
  • Content negotiation between server and client
    • Support for JSON (RFC 8259) and optionally CBOR (RFC 7049) content types via the Accept header with the default config.
  • Conditional requests support, e.g. If-Match or If-Unmodified-Since header utilities.
  • Optional automatic generation of PATCH operations that support:
  • Annotated Go types for input and output models
    • Generates JSON Schema from Go types
    • Static typing for path/query/header params, bodies, response headers, etc.
    • Automatic input model validation & error handling
  • Documentation generation using Stoplight Elements
  • Optional CLI built-in, configured via arguments or environment variables
    • Set via e.g. -p 8000, --port=8000, or SERVICE_PORT=8000
    • Startup actions & graceful shutdown built-in
  • Generates OpenAPI for access to a rich ecosystem of tools
  • Generates JSON Schema for each resource using optional describedby link relation headers as well as optional $schema properties in returned objects that integrate into editors for validation & completion.

This project was inspired by FastAPI. Logo & branding designed by Kari Taylor.

Sponsors

A big thank you to our current & former sponsors!

text
<a href="https://serpapi.com/?utm_source=huma">
	
</a>
<h3>SerpApi: Web Search API</h3>

	Access Google Search, Maps, Shopping, and other search engine data with a simple API by SerpApi.

[Learn more](https://serpapi.com/?utm_source=huma)

Testimonials

This is by far my favorite web framework for Go. It is inspired by FastAPI, which is also amazing, and conforms to many RFCs for common web things ... I really like the feature set, the fact that it [can use] Chi, and the fact that it is still somehow relatively simple to use. I've tried other frameworks and they do not spark joy for me. - Jeb_Jenky

After working with #Golang for over a year, I stumbled upon Huma, the #FastAPI-inspired web framework. It’s the Christmas miracle I’ve been hoping for! This framework has everything! - Hana Mohan

I love Huma. Thank you, sincerely, for this awesome package. I’ve been using it for some time now and it’s been great! - plscott

Thank you Daniel for Huma. Superbly useful project and saves us a lot of time and hassle thanks to the OpenAPI gen β€” similar to FastAPI in Python. - WolvesOfAllStreets

Huma is wonderful, I've started working with it recently, and it's a pleasure, so thank you very much for your efforts πŸ™ - callmemicah

It took us 3 months to build our platform in Python with FastAPI, SQL Alchemy and only 3 weeks to rewrite it in Go with Huma and SQL C. Things just work and I seldomly have to debug where in Python I spent a majority of my time debugging. - Bitclick_

Look at Huma, it's great. A nice slim layer on top of stdlib mux/chi and automatic body and parameter serialization, kinda feels like doing dotnet web APIs, but forces you to actually design request and response structs, which is great imo. - Kirides

Install

Install via go get. Note that Go 1.25 or newer is required.

sh
# After: go mod init ...
go get -u github.com/danielgtaylor/huma/v2

Example

Here is a complete basic hello world example in Huma, that shows how to initialize a Huma app complete with CLI, declare a resource operation, and define its handler function.

go
package main

import (
	"context"
	"fmt"
	"net/http"

	"github.com/danielgtaylor/huma/v2"
	"github.com/danielgtaylor/huma/v2/adapters/humachi"
	"github.com/danielgtaylor/huma/v2/humacli"
	"github.com/go-chi/chi/v5"

	_ "github.com/danielgtaylor/huma/v2/formats/cbor"
)

// Options for the CLI. Pass `--port` or set the `SERVICE_PORT` env var.
type Options struct {
	Port int `help:"Port to listen on" short:"p" default:"8888"`
}

// GreetingOutput represents the greeting operation response.
type GreetingOutput struct {
	Body struct {
		Message string `json:"message" example:"Hello, world!" doc:"Greeting message"`
	}
}

func main() {
	// Create a CLI app which takes a port option.
	cli := humacli.New(func(hooks humacli.Hooks, options *Options) {
		// Create a new router & API
		router := chi.NewMux()
		api := humachi.New(router, huma.DefaultConfig("My API", "1.0.0"))

		// Add the operation handler to the API.
		huma.Get(api, "/greeting/{name}", func(ctx context.Context, input *struct{
			Name string `path:"name" maxLength:"30" example:"world" doc:"Name to greet"`
		}) (*GreetingOutput, error) {
			resp := &GreetingOutput{}
			resp.Body.Message = fmt.Sprintf("Hello, %s!", input.Name)
			return resp, nil
		})

		// Tell the CLI how to start your router.
		hooks.OnStart(func() {
			http.ListenAndServe(fmt.Sprintf(":%d", options.Port), router)
		})
	})

	// Run the CLI. When passed no commands, it starts the server.
	cli.Run()
}

[!TIP]
Replace chi.NewMux() β†’ http.NewServeMux() and humachi.New β†’ humago.New to use the standard library router from Go 1.22+. Just make sure your go.mod has go 1.22 or newer listed in it. Everything else stays the same! Switch whenever you are ready.

You can test it with go run greet.go (optionally pass --port to change the default) and make a sample request using Restish (or curl):

sh
# Get the message from the server
$ restish :8888/greeting/world
HTTP/1.1 200 OK
...
{
	$schema: "http://localhost:8888/schemas/GreetingOutputBody.json",
	message: "Hello, world!"
}

Even though the example is tiny you can also see some generated documentation at http://localhost:8888/docs. The generated OpenAPI is available at http://localhost:8888/openapi.json or http://localhost:8888/openapi.yaml.

Check out the Huma tutorial for a step-by-step guide to get started.

Documentation

See the https://huma.rocks/ website for full documentation in a presentation that's easier to navigate and search then this README. You can find the source for the site in the docs directory of this repo.

Official Go package documentation can always be found at https://pkg.go.dev/github.com/danielgtaylor/huma/v2.

Articles & Mentions

Who is using Huma?

Huma is used by many companies and open-source projects. See the Who is using Huma? page for a list!

Be sure to star the project if you find it useful!


File: docs/docs/features/api-docs.md



Generated API Documentation

Generated API Docs { .hidden }

Huma uses the OpenAPI spec to generate interactive API documentation using third-party tools. By default, Stoplight Elements is used to render the documentation at the API's config.DocsPath which defaults to /docs.

You can switch to other documentation renderers using config.DocsRenderer. The following renderers are supported out of the box:

  • huma.DocsRendererStoplightElements (default)
  • huma.DocsRendererScalar
  • huma.DocsRendererSwaggerUI

!!! info "Disabling the Docs"

text
You can disable the built-in documentation by setting `config.DocsPath` to an empty string, then register your own route on the underlying router. The `DocsRenderer*` functions in [`api.go`](https://github.com/danielgtaylor/huma/blob/main/api.go) show what to return.

!!! warning "Middleware Conflicts"

text
Some middleware can interfere with the documentation renderer's ability to fetch the OpenAPI spec. For example, [go-chi/chi](https://github.com/go-chi/chi)'s `middleware.URLFormat` will rewrite URLs that end in `.json` or `.yaml` (e.g. `/openapi.json` -> `/openapi`), which can lead to 404 errors for the spec. If you encounter this, consider disabling that middleware or configuring it to skip the OpenAPI and documentation paths.

Customizing Documentation

Each renderer accepts its own options through config.DocsRendererConfig. Set it to any value that marshals to JSON (a map[string]any is easiest), and Huma writes it into the docs HTML. Scalar and SwaggerUI support it; Stoplight Elements ignores it.

Scalar Docs

Scalar Docs provide a featureful and customizable API documentation experience that feels similar to Postman in your browser.

go title="code.go"
router := chi.NewRouter()
config := huma.DefaultConfig("Docs Example", "1.0.0")
config.DocsRenderer = huma.DocsRendererScalar

// Optional. Scalar reads these from the `data-configuration` attribute. See
// https://github.com/scalar/scalar/blob/main/documentation/configuration.md
config.DocsRendererConfig = map[string]any{
	"theme":      "mars", // one of: default, alternate, moon, purple, solarized, ...
	"hideModels": true,   // hide the models section
}

api := humachi.New(router, config)

Stoplight Elements

Stoplight Elements is the default renderer, so you get it without setting config.DocsRenderer at all. It doesn't read config.DocsRendererConfig.

go title="code.go"
router := chi.NewRouter()
config := huma.DefaultConfig("Docs Example", "1.0.0")

api := humachi.New(router, config)

SwaggerUI

SwaggerUI is an older but proven documentation generator that is widely used in the industry. It provides a more traditional API documentation experience.

go title="code.go"
router := chi.NewRouter()
config := huma.DefaultConfig("Docs Example", "1.0.0")
config.DocsRenderer = huma.DocsRendererSwaggerUI

// Optional. These fields are merged into the SwaggerUIBundle config object. See
// https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/
config.DocsRendererConfig = map[string]any{
	"defaultModelsExpandDepth": -1,   // hide the models section
	"tryItOutEnabled":          true, // enable "Try it out" by default
	"persistAuthorization":     true, // keep auth across page reloads
}

api := humachi.New(router, config)

Dive Deeper


File: docs/docs/features/auto-patch.md



Auto Patch

Auto Patch { .hidden }

If a GET and a PUT exist for the same resource, but no PATCH exists at server start up, then a PATCH operation can be generated for you to make editing more convenient for clients. You can opt-in to this behavior with the autopatch package:

go
import "github.com/danielgtaylor/huma/v2/autopatch"

// ...

// Later in the code *after* registering operations...
autopatch.AutoPatch(api)

If the GET returns an ETag or Last-Modified header, then these will be used to make conditional requests on the PUT operation to prevent distributed write conflicts that might otherwise overwrite someone else's changes.

The following formats are supported out of the box, selected via the Content-Type header:

!!! info "Merge on Steroids"

text
You can think of the Shorthand Merge Patch as an extension to the JSON merge patch with support for field paths, arrays, and a few other features. Patches like this are possible, appending an item to an array (creating it if needed):

```yaml
{
	foo.bar[]: "baz",
}
```

If the PATCH request has no Content-Type header, or uses application/json or a variant thereof, then JSON Merge Patch is assumed.

Disabling Auto Patch

The auto patch feature can be disabled per resource by setting metadata on an operation:

go title="code.go" hl_lines="7-9"
// Register an operation that won't get a PATCH generated.
huma.Register(api, huma.Operation{
	OperationID: "get-greeting",
	Method:      http.MethodGet,
	Path:        "/greeting/{name}",
	Summary:     "Get a greeting",
	Metadata: map[string]interface{}{
		"autopatch": false,
	},
}, func(ctx context.Context, input *GreetingInput) (*GreetingOutput, error) {
	// ...
})

Dive Deeper


File: docs/docs/features/bring-your-own-router.md



BYOR (Bring Your Own Router)

BYOR (Bring Your Own Router) { .hidden }

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):

!!! info "New Adapters"

text
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:

go title="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:

go title="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.

sh title="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/demo → GET /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.

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

	Request --> Router
	Router -->|http.Requestfiber.Ctxetc| huma.Adapter
	subgraph huma.API
		huma.Adapter -->|huma.Context| OperationHandler
	end

File: docs/docs/features/cli.md



Service CLI

Service CLI { .hidden }

Huma ships with a built-in lightweight utility to wrap your service with a CLI, enabling you to run it with different arguments and easily write custom commands to do things like print out the OpenAPI or run on-demand database migrations.

The CLI options use a similar strategy to input & output structs, enabling you to use the same pattern for validation and documentation of command line arguments. It uses Cobra under the hood, enabling custom commands and including automatic environment variable binding and more.

go title="main.go"
// First, define your input options.
type Options struct {
	Debug bool   `doc:"Enable debug logging"`
	Host  string `doc:"Hostname to listen on."`
	Port  int    `doc:"Port to listen on." short:"p" default:"8888"`
}

func main() {
	// Then, create the CLI.
	cli := humacli.New(func(hooks humacli.Hooks, opts *Options) {
		fmt.Printf("I was run with debug:%v host:%v port%v\n",
			opts.Debug, opts.Host, opts.Port)
	})

	// Run the thing!
	cli.Run()
}

You can then run the CLI and see the results:

sh title="terminal"
// Run with defaults
$ go run main.go
I was run with debug:false host: port:8888

// Run with options
$ go run main.go --debug=true --host=localhost --port=8000
I was run with debug:true host:localhost port:8000

To do useful work, you will want to register a handler for the default start command and optionally a way to gracefully shutdown the server:

go title="main.go"
cli := humacli.New(func(hooks humacli.Hooks, opts *Options) {
	// Set up the router and API
	// ...

	// Create the HTTP server.
	server := http.Server{
		Addr:    fmt.Sprintf(":%d", options.Port),
		Handler: router,
	}

	hooks.OnStart(func() {
		// Start your server here
		server.ListenAndServe()
	})

	hooks.OnStop(func() {
		// Gracefully shutdown your server here
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		server.Shutdown(ctx)
	})
})

!!! info "Naming"

text
Option fields are automatically converted to `--kebab-casing` for use on the command line. If you want to use a different name, use the `name` struct tag to override the default behavior!

Passing Options

Options can be passed explicitly as command-line arguments to the service or they can be provided by environment variables prefixed with SERVICE_. For example, to run the service on port 8000:

bash
# Example passing command-line args
$ go run main.go --port=8000

# Short arguments are also supported
$ go run main.go -p 8000

# Example passing by environment variables
$ SERVICE_PORT=8000 go run main.go

!!! warning "Precedence"

text
If both environment variable and command-line arguments are present, then command-line arguments take priority.

Custom Options

Custom options are defined by adding to your options struct. The following types are supported:

Type Example Inputs
bool true, false
int / int64 1234, 5, -1
string prod, http://api.example.tld/
time.Duration 500ms, 3s, 1h30m

The following struct tags are available:

Tag Description Example
default Default value (parsed automatically) default:"123"
doc Describe the option doc:"Who to greet"
name Override the name of the option name:"my-option-name"
short Single letter short name for the option short:"p" for -p

Here is an example of how to use them:

go title="main.go"
type Options struct {
	Debug bool   `doc:"Enable debug logging"`
	Host  string `doc:"Hostname to listen on."`
	Port  int    `doc:"Port to listen on." short:"p" default:"8888"`
}

Custom Commands

You can access the root cobra.Command via cli.Root() and add new custom commands via cli.Root().AddCommand(...). For example, to have a command print out the generated OpenAPI:

go title="main.go"
var api huma.API

// ... set up the CLI, create the API wrapping the router ...

cli.Root().AddCommand(&cobra.Command{
	Use:   "openapi",
	Short: "Print the OpenAPI spec",
	Run: func(cmd *cobra.Command, args []string) {
		b, err := api.OpenAPI().YAML()
		if err != nil {
			panic(err)
		}
		fmt.Println(string(b))
	},
})

!!! info "Note"

text
You can use `api.OpenAPI().DowngradeYAML()` to output OpenAPI 3.0 instead of 3.1 for tools that don't support 3.1 yet.

Now you can run your service and use the new command: go run . openapi. Notice that it never starts the server; it just runs your command handler code. Some ideas for custom commands:

  • Print the OpenAPI spec
  • Print JSON Schemas
  • Run database migrations
  • Run customer scenario tests
  • Bundle common actions into a single utility command, like adding a new user

Custom Commands with Options

If you want to access your custom options struct with custom commands, use the huma.WithOptions(func(cmd *cobra.Command, args []string, options *YourOptions)) func(cmd *cobra.Command, args []string) utility function. It ensures the options are parsed and available before running your command.

!!! info "More Customization"

text
You can also overwrite `cli.Root().Run` to completely customize how you run the server. Or just ditch the `cli` package altogether!

App Name & Version

You can set the app name and version to be used in the help output and version command. By default, the app name is the name of the binary and the version is unset. You can set them using the root cobra.Command's Use and Version fields:

go title="main.go"
// cli := humacli.New(...)

cmd := cli.Root()
cmd.Use = "appname"
cmd.Version = "1.0.1"

cli.Run()

Then you will see something like this:

sh title="terminal"
$ go run ./demo --help
Usage:
  appname [flags]

Flags:
  -h, --help            help for appname
  -p, --port int         (default 8888)
  -v, --version         version for appname

$ go run ./demo --version
appname version 1.0.1

Dive Deeper


File: docs/docs/features/cli-auto-config.md



CLI AutoConfig

Huma includes built-in support for an OpenAPI 3 extension that enables CLI auto-configuration. This allows tools like Restish to automatically configure themselves to talk to your API with the correct endpoints, authentication mechanism, etc without the user needing to know anything about your API.

go
o := api.OpenAPI()
o.Components.SecuritySchemes["my-scheme"] = &huma.SecurityScheme{
	Type: "oauth2",
	// ... security scheme definition ...
}
o.Extensions["x-cli-config"] = huma.AutoConfig{
	Security: "my-scheme",
	Params: map[string]string{
		"client_id": "abc123",
		"authorize_url": "https://example.tld/authorize",
		"token_url": "https://example.tld/token",
		"scopes": "read,write",
	}
}

See the CLI AutoConfiguration documentation for more info, including how to ask the user for custom parameters.


File: docs/docs/features/conditional-requests.md



Conditional Requests

Conditional Requests { .hidden }

There are built-in utilities for handling conditional requests, which serve two broad purposes:

  1. Sparing bandwidth on reading a document that has not changed, i.e. "only send if the version is different from what I already have".
  2. Preventing multiple writers from clobbering each other's changes, i.e. "only save if the version on the server matches what I saw last".

Adding support for handling conditional requests requires four steps:

  1. Import the github.com/danielgtaylor/huma/v2/conditional package.
  2. (optional) Add the response definition (304 Not Modified for reads or 412 Precondition Failed for writes)
  3. Add conditional.Params to your input struct.
  4. Check if conditional params were passed and handle them. The HasConditionalParams() and PreconditionFailed(...) methods can help with this.

Example

Implementing a conditional read might look like:

go
huma.Register(api, huma.Operation{
	OperationID: "get-resource",
	Method:      http.MethodGet,
	Path:        "/resource",
	Summary:     "Get a resource",
}, func(ctx context.Context, input struct {
	conditional.Params
}) (*YourOutput, error) {
	if input.HasConditionalParams() {
		// TODO: Get the ETag and last modified time from the resource.
		etag := ""
		modified := time.Time{}

		// If preconditions fail, abort the request processing. Response status
		// codes are already set for you, but you can optionally provide a body.
		// Returns an HTTP 304 not modified.
		if err := input.PreconditionFailed(etag, modified); err != nil {
			return err
		}

		// Otherwise do the normal request processing here...
		// ...
	}
})

!!! info "Conditional Request Efficiency"

text
Note that it is more efficient to construct custom DB queries to handle conditional requests, however Huma is not aware of your database. The built-in conditional utilities are designed to be generic and work with any data source, and are a quick and easy way to get started with conditional request handling.

Dive Deeper


File: docs/docs/features/groups.md



Groups

Groups { .hidden }

Operations can be grouped under common route prefixes and share middleware, operation modifier functions, and response transformers. This is done using the huma.Group wrapper around a huma.API instance, which can then be passed to huma.Register and its convenience wrappers like huma.Get, huma.Post, etc.

go
grp := huma.NewGroup(api, "/v1")
grp.UseMiddleware(authMiddleware)

huma.Get(grp, "/users", func(ctx context.Context, input *struct{}) (*UsersResponse, error) {
	// ...
})

The above example will register a GET /v1/users operation with the authMiddleware running before the operation handler.

!!! info "Groups & Documentation"

text
Groups assume that `huma.Register` or one of its convenience wrappers is used to register operations. If you are not, then you may need to invoke `group.DocumentOperation(*huma.Operation)` to ensure that the operation is documented correctly.

Group Features

Groups support the following features:

  • One or more path prefixes for all operations in the group.
  • Middleware that runs before each operation in the group.
  • Operation modifiers that run at operation registration time.
  • Response transformers that run after each operation in the group.

Prefixes

Groups can have one or more path prefixes that are prepended to all operations in the group. This is useful for grouping related operations under a common prefix and is typically done with a single prefix.

go
grp := huma.NewGroup(api, "/prefix1", "/prefix2", "...")

This is just a convenience for the following equivalent code:

go
grp := huma.NewGroup(api)
grp.UseModifier(huma.PrefixModifier("/prefix1", "/prefix2", "..."))

The built-in huma.PrefixModifier will adjust the operation's ID and tags when more than one prefix is used. If you wish to customize this behavior, you can write your own operation modifier.

Middleware

Middleware functions are run before each operation handler in the group. They can be used for common tasks like authentication, logging, and error handling. Middleware functions are registered using the UseMiddleware method on a group.

go
grp.UseMiddleware(func(ctx huma.Context, next func(huma.Context)) {
	// Do something before the operation runs
	next(ctx)
})

Operation Modifiers

Operation modifiers are functions that run at operation registration time. They can be used to modify the operation before it is registered. Operation modifiers are registered using the UseModifier method on a group.

go
grp.UseModifier(func(op *huma.Operation, next func(*huma.Operation)) {
	op.Summary = "A summary for all operations in this group"
	op.Tags = []string{"my-tag"}
    next(op)
})

There is also a simplified form you can use:

go
grp.UseSimpleModifier(func(op *huma.Operation) {
	op.Summary = "A summary for all operations in this group"
	op.Tags = []string{"my-tag"}
})

Response Transformers

Response transformers are functions that run after each operation handler in the group. They can be used to modify the response before it is returned to the client. Response transformers are registered using the UseResponseTransformer method on a group.

go
grp.UseTransformer(func(ctx huma.Context, status string, v any) (any, error) {
	// Do something with the output
	return output, nil
})

Customizing Documentation

Groups implement huma.OperationDocumenter which bypasses the normal flow of documentation generation and instead calls a function. This allows you to customize the documentation for all operations in the group. You can override the DocumentOperation method to customize the documentation if needed:

go
type MyGroup huma.Group

func (g *MyGroup) DocumentOperation(op *huma.Operation) {
	g.ModifyOperation(op, func(op *huma.Operation) {
		if documenter, ok := g.API.(huma.OperationDocumenter); ok {
			// Support nested operation documenters (i.e. groups of groups).
			documenter.DocumentOperation(op)
		} else {
			// Default behavior to add operations.
			if op.Hidden {
				return
			}
			g.OpenAPI().AddOperation(op)
		}
	})
}

Dive Deeper


File: docs/docs/features/index.md



Features

Huma is a modern, simple, fast & flexible micro framework for building HTTP REST/RPC APIs in Golang backed by OpenAPI 3 and JSON Schema. Pronounced IPA: /'hjuːmΙ‘/. The goals of this project are to provide:

  • A modern REST or HTTP RPC API backend framework for Go developers
  • Incremental adoption for teams with existing services
    • Bring your own router, middleware, and logging/metrics
    • Extensible OpenAPI & JSON Schema layer to document existing routes
  • Guard rails to prevent common mistakes
  • Documentation that can't get out of date
  • High-quality generated developer tooling

Features include:

  • Declarative interface on top of your router of choice:
    • Operation & model documentation
    • Request params (path, query, or header)
    • Request body
    • Responses (including errors)
    • Response headers
  • JSON Errors using RFC9457 and application/problem+json by default (but can be changed)
  • Per-operation request size limits with sane defaults
  • Content negotiation between server and client
    • Support for JSON (RFC 8259) and optional CBOR (RFC 7049) content types via the Accept header with the default config.
  • Conditional requests support, e.g. If-Match or If-Unmodified-Since header utilities.
  • Optional automatic generation of PATCH operations that support:
  • Annotated Go types for input and output models
    • Generates JSON Schema from Go types
    • Static typing for path/query/header params, bodies, response headers, etc.
    • Automatic input model validation & error handling
  • Documentation generation using Stoplight Elements
  • Optional CLI built-in, configured via arguments or environment variables
    • Set via e.g. -p 8000, --port=8000, or SERVICE_PORT=8000
    • Startup actions & graceful shutdown built-in
  • Generates OpenAPI for access to a rich ecosystem of tools
  • Generates JSON Schema for each resource using optional describedby link relation headers as well as optional $schema properties in returned objects that integrate into editors for validation & completion.

!!! info "Mascot"

text
Hi there! I'm the happy Huma whale here to provide help. You'll see me leave helpful tips throughout the docs.

Official Go package documentation can always be found at https://pkg.go.dev/github.com/danielgtaylor/huma/v2. Read on for an introduction to the various features available in Huma.


File: docs/docs/features/json-schema-registry.md



JSON Schema

Using the default Huma config (or manually via the huma.SchemaLinkTransformer), each resource operation returns a describedby HTTP link relation header which references a JSON-Schema file. These schemas use the config.SchemasPath to serve their content. For example:

http title="http response"
Link: </schemas/Note.json>; rel="describedby"

Object resources (i.e. not arrays or simple scalars) can also optionally return a $schema property with such a link, which enables the described-by relationship to outlive the HTTP request (i.e. saving the body to a file for later editing) and enables some editors like VSCode to provide code completion and validation as you type.

json title="response.json"
{
	"$schema": "http://localhost:8888/schemas/Note.json",
	"title": "I am a note title",
	"contents": "Example note contents",
	"labels": ["todo"]
}

Operations which accept objects as input will ignore the $schema property, so it is safe to submit back to the API, aka "round-trip" the data.

Disabling `$schema`

If you want to disable the $schema property and the Link header in responses, you can set the CreateHooks to nil in your configuration before creating the API. This prevents the default SchemaLinkTransformer from being registered.

go
config := huma.DefaultConfig("My API", "1.0.0")
config.CreateHooks = nil

// Create the API using the config...

Alternatively, you can manually manage your Transformers and OnAddOperation hooks in a custom configuration if you only want to disable one of them.

!!! info "Editing"

text
The `$schema` field is incredibly powerful when paired with Restish's [edit](https://rest.sh/docs/reference/edit-command/) command, giving you a quick and easy way to edit strongly-typed resources in your favorite editor.

Schema Registry

Huma uses a customizable registry to keep track of all the schemas that have been generated from Go structs. This is used to avoid generating the same schema multiple times, and to provide a way to reference schemas by name for OpenAPI operations & hosted JSON Schemas.

The default schema implementation uses a map to store schemas by name,generated from the Go type name without the package name. This supports recursive schemas and generates simple names like Thing or ThingList.

!!! warning "Schema Names"

text
Note that by design the default registry does **not** support multiple models with the same name in different packages. For example, adding both `foo.Thing` and `bar.Thing` will result in a conflict. You can work around this by defining a new type like `type BarThing bar.Thing` and using that instead, or using a custom [registry naming function](https://pkg.go.dev/github.com/danielgtaylor/huma/v2#DefaultSchemaNamer).

Custom Registry

You can create your own registry with custom behavior by implementing the huma.Registry interface and setting it on config.OpenAPI.Components.Schemas when creating your API.

Dive Deeper