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
- Described by OpenAPI 3.1 & JSON Schema
- 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+jsonby default (but can be changed) - Per-operation request size limits with sane defaults
- Content negotiation between server and client
- Conditional requests support, e.g.
If-MatchorIf-Unmodified-Sinceheader utilities. - Optional automatic generation of
PATCHoperations 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, orSERVICE_PORT=8000 - Startup actions & graceful shutdown built-in
- Set via e.g.
- Generates OpenAPI for access to a rich ecosystem of tools
- Mocks with API Sprout or Prism
- SDKs with OpenAPI Generator or oapi-codegen
- CLI with Restish
- And plenty more
- Generates JSON Schema for each resource using optional
describedbylink relation headers as well as optional$schemaproperties 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!
<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)- @bclements
- @bekabaz
- @victoraugustolls
- @phoenixtechnologies-io
- @chenjr0719
- @vinogradovkonst
- @miyamo2
- @nielskrijger
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.
# After: go mod init ...
go get -u github.com/danielgtaylor/huma/v2Example
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.
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]
Replacechi.NewMux()βhttp.NewServeMux()andhumachi.Newβhumago.Newto use the standard library router from Go 1.22+. Just make sure yourgo.modhasgo 1.22or 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):
# 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
- APIs in Go with Huma 2.0
- Reducing Go Dependencies: A case study of dependency reduction in Huma
- Golang News & Libs & Jobs shared on Twitter/X
- Featured in Go Weekly #495 & #498
- Bump.sh Deploying Docs from Huma
- Mentioned in Composable HTTP Handlers Using Generics
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.DocsRendererScalarhuma.DocsRendererSwaggerUI
!!! info "Disabling the Docs"
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"
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.
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.
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.
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
- Reference
huma.Configthe API confighuma.DefaultConfigthe default API config
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:
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:
- JSON Merge Patch
application/merge-patch+json - Shorthand Merge Patch
application/merge-patch+shorthand - JSON Patch
application/json-patch+json
!!! info "Merge on Steroids"
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:
// 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
- Reference
autopatchpackage
- External Links
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):
- BunRouter via
humabunrouter - chi via
humachi - Echo via
humaecho - Fiber via
humafiber - gin via
humagin - Go 1.22+
http.ServeMuxviahumago(requiresgo 1.22or newer ingo.mod) - gorilla/mux via
humamux - httprouter via
humahttprouter
!!! info "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:
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:
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.
# Call the demo operation
restish :8888/api/demo
# Get the OpenAPI
restish :8888/api/openapi.yamlThe 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.
graph LR
Request([Request])
OperationHandler[Operation Handler]
Request --> Router
Router -->|http.Requestfiber.Ctxetc| huma.Adapter
subgraph huma.API
huma.Adapter -->|huma.Context| OperationHandler
end- Features
- Reference
huma.Contexta router-agnostic request/response contexthuma.Adapterthe router-agnostic adapter interfacehuma.APIthe API instancehuma.NewAPIcreates an API instance (called by adapters)huma.Registerregisters new operationshuma.OpenAPIconfigures things like the server URLs & base path
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.
// 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:
// 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:8000To do useful work, you will want to register a handler for the default start command and optionally a way to gracefully shutdown the server:
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"
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:
# 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"
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:
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:
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"
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"
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:
// cli := humacli.New(...)
cmd := cli.Root()
cmd.Use = "appname"
cmd.Version = "1.0.1"
cli.Run()Then you will see something like this:
$ 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.1Dive Deeper
- Tutorial
- Service Configuration Tutorial includes a working CLI example
- How-To
- Graceful Shutdown on service stop
- Reference
humacli.CLIthe CLI instancehumacli.Newcreates a new CLI instancehumacli.Hooksfor startup / shutdownhumacli.WithOptionswraps a command with options parsinghuma.APIthe API instance
- External Links
- Cobra CLI library
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.
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:
- Sparing bandwidth on reading a document that has not changed, i.e. "only send if the version is different from what I already have".
- 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:
- Import the
github.com/danielgtaylor/huma/v2/conditionalpackage. - (optional) Add the response definition (
304 Not Modifiedfor reads or412 Precondition Failedfor writes) - Add
conditional.Paramsto your input struct. - Check if conditional params were passed and handle them. The
HasConditionalParams()andPreconditionFailed(...)methods can help with this.
Example
Implementing a conditional read might look like:
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"
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
- Reference
conditionalpackageconditional.Params
- External Links
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.
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"
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.
grp := huma.NewGroup(api, "/prefix1", "/prefix2", "...")This is just a convenience for the following equivalent code:
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.
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.
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:
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.
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:
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
- Features
- Operations registration & workflows
- Middleware for operations
- Response Transformers to modify response bodies
- Reference
huma.Registerregister an operationhuma.Middlewareslist of middlewarehuma.Transformerresponse transformershuma.OperationDocumenterto customize OpenAPI generationhuma.APIthe API instance
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
- Described by OpenAPI 3.1 & JSON Schema
- 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+jsonby default (but can be changed) - Per-operation request size limits with sane defaults
- Content negotiation between server and client
- Conditional requests support, e.g.
If-MatchorIf-Unmodified-Sinceheader utilities. - Optional automatic generation of
PATCHoperations 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, orSERVICE_PORT=8000 - Startup actions & graceful shutdown built-in
- Set via e.g.
- Generates OpenAPI for access to a rich ecosystem of tools
- Mocks with API Sprout or Prism
- SDKs with OpenAPI Generator or oapi-codegen
- CLI with Restish
- And plenty more
- Generates JSON Schema for each resource using optional
describedbylink relation headers as well as optional$schemaproperties in returned objects that integrate into editors for validation & completion.
!!! info "Mascot"
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:
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.
{
"$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.
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"
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"
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
- Reference
huma.Schemais a JSON Schemahuma.Registrygenerates & stores JSON Schemashuma.DefaultSchemaNamernames schemas from typeshuma.Configthe API confighuma.DefaultConfigthe default API confighuma.OpenAPIthe OpenAPI spechuma.Componentscontains theSchemasregistry
- External Links
- See Also
- Model Validation utility to validate custom JSON objects