6.8 KiB
Huma REST API Framework
A modern, simple, fast & opinionated REST API framework for Go. The goals of this project are to provide:
- A modern REST API backend framework for Go developers
- Described by OpenAPI 3 & JSON Schema
- First class support for middleware, JSON, and other features
- Documentation that can't get out of date
- High-quality developer tooling
Features include:
- Declarative interface on top of Gin - Operation & model documentation - Request params (path, query, or header) - Request body - Responses (including errors)
- Annotated Go types for input and output models
- Automatic input model validation
- Documentation generation using Redoc
- Generates OpenAPI JSON for access to a rich ecosystem of tools
- Mocks with API Sprout
- SDKs with OpenAPI Generator
- CLIs with OpenAPI CLI Generator
- And plenty more
This project was inspired by FastAPI, Gin, and countless others.
Concepts & Example
REST APIs are composed of operations against resources and can include descriptions of various inputs and possible outputs. Huma uses standard Go types and a declarative API to capture those descriptions in order to provide a combination of a simple interface and idiomatic code leveraging Go's strong typing.
Let's start by building Huma's equivalent of a "Hello, world" program. First, you'll need to know a few basic things:
- What's this API called?
- How will you call the hello operation?
- What will the response of our hello operation look like?
You use Huma concepts to answer those questions, and then write your operation's handler function. Below is the full working example:
package main
import (
"net/http"
"github.com/danielgtaylor/huma"
)
func main() {
// Create a new router and give our API a title and version.
r := huma.NewRouter(&huma.OpenAPI{
Title: "My API",
Version: "1.0.0",
})
// Create the "hello" operation via `GET /hello`.
r.Register(&huma.Operation{
Method: http.MethodGet,
Path: "/hello",
Description: "Basic hello world",
// Every response definition includes the HTTP status code to return, the
// content type to use, and a description for documentation.
Responses: []*huma.Response{
huma.ResponseText(http.StatusOK, "Successful hello response"),
},
// The Handler is the operation's implementation. In this example, we
// are just going to return the string "hello", but you could fetch
// data from your datastore or do other things here.
Handler: func() string {
return "Hello, world"
},
})
// Start the server on http://localhost:8888/
r.Run("0.0.0.0:8888")
}
Save this file as hello/main.go
. Run it and then try to access the API with HTTPie (or curl):
# Grab reflex to enable reloading the server on code changes:
$ go get github.com/cespare/reflex
# Run the server
$ reflex -s go run hello/main.go
# Make the request (in another tab)
$ http :8888/hello
HTTP/1.1 200 OK
Content-Length: 5
Content-Type: text/plain
Date: Mon, 09 Mar 2020 04:28:13 GMT
hello
The server works and responds as expected. Nothing too interesting here, so let's change that.
Parameters
Make the hello operation take an optional name
parameter. Optional parameters are passed via query string arguments in the URL. Add a new huma.QueryParam
to the operation and then update the handler function to take a name
parameter.
r.Register(&huma.Operation{
Method: http.MethodGet,
Path: "/hello",
Description: "Basic hello world",
Params: []*huma.Param{
huma.QueryParam("name", "Who to greet", "world"),
},
Responses: []*huma.Response{
huma.ResponseText(http.StatusOK, "Successful hello response"),
},
Handler: func(name string) string {
return "Hello, " + name
},
})
Try making another request after saving the file (the server should automatically restart):
# Make the request without a name
$ http :8888/hello
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain
Date: Mon, 09 Mar 2020 04:35:42 GMT
Hello, world
# Make the request with a name
$ http :8888/hello?name=Daniel
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain
Date: Mon, 09 Mar 2020 04:35:42 GMT
Hello, Daniel
Nice work! Operating on strings is fun but let's throw some JSON into the mix next.
Request & Response Models
Update the response to use JSON by defining a model. Models are annotated Go structures which you've probably seen before when marshalling and unmarshalling to/from JSON. Create a silly one that contains just the hello message:
// HelloResponse returns the message for the hello operation.
type HelloResponse struct {
Message string `json:"message" description:"Greeting message"`
}
This uses Go struct field tags to add additional information and will generate JSON-Schema for you. With just a couple small changes you now will have a JSON API:
- Change the response type to JSON
- Return an instance of
HelloResponse
in the handler
r.Register(&huma.Operation{
Method: http.MethodGet,
Path: "/hello",
Description: "Basic hello world",
Params: []*huma.Param{
huma.QueryParam("name", "Who to greet", "world"),
},
Responses: []*huma.Response{
huma.ResponseJSON(http.StatusOK, "Successful hello response"),
},
Handler: func(name string) *HelloResponse {
return &HelloResponse{
Message: "Hello, " + name,
}
},
})
Try saving the file and making another request:
# Make the request with a name
$ http :8888/hello
HTTP/1.1 200 OK
Content-Length: 27
Content-Type: application/json; charset=utf-8
Date: Mon, 09 Mar 2020 05:00:14 GMT
{
"message": "Hello, world"
}
Great! But that's not all! Take a look at two more automatically-generated routes. The first shows you documentation about your API, while the second is the OpenAPI 3 spec file you can use to integrate with other tooling to generate client SDKs, CLI applications, and more.
- Documenation: http://localhost:8888/docs
- OpenAPI 3 spec: http://localhost:8888/openapi.json
For the docs, you should see something like this:

Request models are essentially the same. Just define an extra input argument to the handler funtion and you get automatic loading and validation.
More docs coming soon.