Automate SDK generation for Go Routers (Chi, Gin, etc) with Huma APIs
Supported SDK languages:
TypeScript / Javascript | Java / Kotlin | Python | C# | Go | PHP |
---|---|---|---|---|---|
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
- Before getting started make sure you have a liblab account and the liblab CLI installed.
- If you don't have an existing project but want to try out liblab then check out our standalone tutorials.
Providing an SDK for your API can significantly ease the development process for users, ensuring quicker integration and encouraging wider adoption due to the streamlined development experience.
In this doc you'll learn how to take your existing Huma API and generate user-friendly SDKs for most major programming languages using the liblab SDK generator. After a short bit of setup you'll have a pipeline that will automatically generate SDKs for your API whenever you make changes.
Initializing the SDK
First, create a new directory to store your SDK. This directory should be separate from your existing project directory:
mkdir Huma-sdk
cd Huma-sdk
And initialize your project to create a liblab.config.json
file:
liblab init -y
Getting an OpenAPI Spec File
In order to auto-generate SDKs from a Huma project you need an OpenAPI Spec File. The OpenAPI file contains information about your API, such as servers, paths, operations, parameters, responses, and security schemas.
Huma has integrated support for OpenAPI and generates OpenAPI 3.1 specs in JSON and YAML formts.
Installation
Huma can be used as a standalone framework, but it is also supports working with other routers. Below we show how to integrate Huma with a Chi router. Huma supports other routers such as native Go http, Gin, Echo, BunRouter, httprouter, Fiber, and gorilla/mux.
If you need to integrate with one of these options then check Huma's bring your own router documentation for instructions on how to integrate it with your existing router.
First add Huma to your Go project if it hasn't been already. If your project already uses Huma then skip to the next step and make sure your API Configuration is setup.
go get -u github.com/danielgtaylor/huma/v2
Setting Up the API Configuration
Now you'll setup your huma Config. To start, define the API name and version number.
package main
import (
"context"
"net/http"
"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/adapters/humachi"
"github.com/go-chi/chi/v5"
)
func main() {
// Create a new Huma API configuration
config := huma.DefaultConfig("My API", "1.0.0")
// Initialize the Chi router
r := chi.NewRouter()
// Initialize the API with Chi router
api := humachi.New(r, config)
// Start the server
http.ListenAndServe(":8888", api)
}
By default, Huma automatically generates and serves OpenAPI 3.1 Specifications at /openapi.json
(ex. http://localhost:8888/openapi.json
).
This basic setup creates an API with a title and version but you'll want to extend it with additional metadata such as a Description
, Contact
, Server
information, and Authentication (ie. SecuritySchemes
) as in the example below:
// ...
config := huma.DefaultConfig("My API", "1.0.0")
// Add metadata
config.Info.Description = "This is a sample API to demonstrate Huma capabilities."
config.Info.TermsOfService = "https://example.com/terms/"
config.Info.Contact = &huma.Contact{
Name: "API Support",
URL: "https://example.com/support",
Email: "[email protected]",
}
// Add server information
config.Servers = []*huma.Server{
{
URL: "https://api.example.com/v1",
Description: "Sandbox server",
},
}
// Define tags for organizing endpoints
config.Tags = []*huma.Tag{
{
Name: "greeting",
Description: "Endpoints related to greeting messages",
},
}
// Define a basic authentication security scheme
config.Components.SecuritySchemes = map[string]*huma.SecurityScheme{
"basicAuth": {
Type: "http",
Scheme: "basic",
},
}
// ...
The following options are available in huma.Config
. For more details on available configuration options, check out the official Huma documentation.
Info
: User-readable information about your API.Title
: The title of your API.Version
: The version of your API.Description
: A brief description of your API, explaining its purpose and functionality.TermsOfService
: A URL linking to the terms of service for your API.Contact
(huma.Contact): Contact details for API support, including:
Servers
*([]huma.Server): A list of server URLs where the API is hosted, with descriptions:URL
: The base URL of the API (e.g.,https://api.example.com/v1
).Description
: A short description of the server (e.g., "Production server").
Tags
*([]huma.Tag): A list of tags for organizing API endpoints:Name
: The name of the tag (e.g., "greeting").Description
: A description of the category (e.g., "Endpoints related to greeting messages").
Components.SecuritySchemes
*(map[string]huma.SecurityScheme): Defines the security mechanisms supported by the API:Type
: The type of security scheme (e.g., "http").Scheme
: The authentication scheme (e.g., "basic" for HTTP Basic Authentication).
You don't need to annotate every part of your API at once. You've already defined enough of your API to take the generated openapi.json specification file and you could skip to the copy your spec section to try out generating an SDK with liblab.
This is an iterative process and you can always come back to it later.
Defining API Operations
To generate the best SDK and documentation possible for your SDK's users you should provide additional metadata alongside your route definitions. You can do this by using huma.Register
and huma.Operation
to register the various operations your API provides:
OperationID
: A unique identifier for the operation.Method
: The HTTP method (e.g.,GET
).Path
: The API endpoint, which may include parameters (e.g.,/{name}
).Summary
andDescription
: Provide a concise explanation of the operation.Tags
: Categorize endpoints for better documentation organization.Security
(optional): A map of security mechanisms that can be used for the endpoint.Responses
(optional): A map of possible HTTP status codes and responses.
The following code snippet demonstrates how to define a simple greeting API using Huma. It provides an endpoint, /greeting/{name}
, which accepts a name as input and returns a Hello, <name>!
message. A validation rule restricts the name
to a maximum of 30 characters. The example uses specific types (GreetingInput
, GreetingOutput
, ErrorResponse
) to specify the input parameters and endpoint responses.
// ...
// GreetingInput defines the input parameters for the greeting operation.
type GreetingInput struct {
// Name is a path parameter that specifies who to greet.
Name string `path:"name" example:"world" doc:"The name to greet"`
}
// GreetingOutput represents the greeting operation response.
type GreetingOutput struct {
Body struct {
Message string `json:"message" example:"Hello, world!" doc:"Greeting message"`
}
}
// ErrorResponse represents a standard error response.
type ErrorResponse struct {
Error string `json:"error" example:"Name too long" doc:"Error message"`
}
// ...
func main() {
//...
// Initialize the API with Chi router
api := humachi.New(r, config)
// Register a GET operation.
huma.Register(api, huma.Operation{
OperationID: "get-greeting",
Method: http.MethodGet,
Path: "/greeting/{name}",
Summary: "Get a personalized greeting",
Description: "Returns a greeting for the specified name.",
Tags: []string{"Greetings"},
Responses: map[string]*huma.Response{
"200": {
Description: "Successful greeting response",
// Huma generate the schema from the Go type automatically.
},
"400": {
Description: "Invalid request parameters",
Content: map[string]*huma.MediaType{
"application/json": {
// Huma uses the custom error type to generate the schema.
Schema: huma.SchemaFromType(api.OpenAPI().Components.Schemas, reflect.TypeOf(ErrorResponse{})),
},
},
},
},
}, func(ctx context.Context, input *GreetingInput) (*GreetingOutput, error) {
if len(input.Name) > 30 {
// Return a custom error using the MyError type.
return nil, huma.NewError(http.StatusBadRequest, "Name too long")
}
resp := &GreetingOutput{}
resp.Body.Message = "Hello, " + input.Name + "!"
return resp, nil
})
// ...
}
For more information about operation declaration parameters and syntax, you can access the Official Huma documentation
Complete Example
Putting the above steps together produces a complete example of generating an Openapi spec in a Go project using Huma.
package main
import (
"context"
"net/http"
"reflect"
"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/adapters/humachi"
"github.com/go-chi/chi/v5"
)
// GreetingInput defines the input parameters for the greeting operation.
type GreetingInput struct {
// Name is a path parameter that specifies who to greet.
Name string `path:"name" example:"world" doc:"The name to greet"`
}
// GreetingOutput represents the greeting operation response.
type GreetingOutput struct {
Body struct {
Message string `json:"message" example:"Hello, world!" doc:"Greeting message"`
}
}
// ErrorResponse represents a standard error response.
type ErrorResponse struct {
Error string `json:"error" example:"Name too long" doc:"Error message"`
}
// MyError wraps ErrorResponse and implements the huma.StatusError interface.
type MyError struct {
Status int
ErrorResponse
}
func (e *MyError) Error() string {
return e.ErrorResponse.Error
}
func (e *MyError) GetStatus() int {
return e.Status
}
func main() {
// Create a new Huma API configuration.
config := huma.DefaultConfig("My API", "1.0.0")
// Add metadata
config.Info.Description = "This is a sample API to demonstrate Huma capabilities."
config.Info.TermsOfService = "https://example.com/terms/"
config.Info.Contact = &huma.Contact{
Name: "API Support",
URL: "https://example.com/support",
Email: "[email protected]",
}
// Define server information.
config.Servers = []*huma.Server{
{
URL: "http://localhost:8888",
Description: "Local development server",
},
}
// Define tags for organizing endpoints
config.Tags = []*huma.Tag{
{
Name: "greeting",
Description: "Endpoints related to greeting messages",
},
}
// Define a basic authentication security scheme
config.Components.SecuritySchemes = map[string]*huma.SecurityScheme{
"basicAuth": {
Type: "http",
Scheme: "basic",
},
}
// Override Huma's error creator to use our custom error type.
huma.NewError = func(status int, msg string, errs ...error) huma.StatusError {
return &MyError{
Status: status,
ErrorResponse: ErrorResponse{
Error: msg,
},
}
}
// Initialize the Chi router.
r := chi.NewRouter()
// Initialize the API with the Chi router.
api := humachi.New(r, config)
// Register a GET operation.
huma.Register(api, huma.Operation{
OperationID: "get-greeting",
Method: http.MethodGet,
Path: "/greeting/{name}",
Summary: "Get a personalized greeting",
Description: "Returns a greeting for the specified name.",
Tags: []string{"Greetings"},
Responses: map[string]*huma.Response{
"200": {
Description: "Successful greeting response",
// Huma generate the schema from your Go type automatically.
},
"400": {
Description: "Invalid request parameters",
Content: map[string]*huma.MediaType{
"application/json": {
// Huma uses the custom error type to generate the schema.
Schema: huma.SchemaFromType(api.OpenAPI().Components.Schemas, reflect.TypeOf(ErrorResponse{})),
},
},
},
},
}, func(ctx context.Context, input *GreetingInput) (*GreetingOutput, error) {
if len(input.Name) > 30 {
// Return a custom error using the MyError type.
return nil, huma.NewError(http.StatusBadRequest, "Name too long")
}
resp := &GreetingOutput{}
resp.Body.Message = "Hello, " + input.Name + "!"
return resp, nil
})
// Start the server.
addr := ":8888"
println("Server is running on http://localhost" + addr)
http.ListenAndServe(addr, r)
}
The Huma's error creator was overrided to use custom error types.
Click to see the OpenAPI spec example
{
"components": {
"schemas": {
"GreetingOutputBody": {
"additionalProperties": false,
"properties": {
"$schema": {
"description": "A URL to the JSON Schema for this object.",
"examples": [
"http://localhost:8888/schemas/GreetingOutputBody.json"
],
"format": "uri",
"readOnly": true,
"type": "string"
},
"message": {
"description": "Greeting message",
"examples": [
"Hello, world!"
],
"type": "string"
}
},
"required": [
"message"
],
"type": "object"
},
"MyError": {
"additionalProperties": false,
"properties": {
"Status": {
"format": "int64",
"type": "integer"
},
"error": {
"description": "Error message",
"examples": [
"Name too long"
],
"type": "string"
}
},
"required": [
"Status",
"error"
],
"type": "object"
}
},
"securitySchemes": {
"basicAuth": {
"scheme": "basic",
"type": "http"
}
}
},
"info": {
"contact": {
"email": "[email protected]",
"name": "API Support",
"url": "https://example.com/support"
},
"description": "This is a sample API to demonstrate Huma capabilities.",
"termsOfService": "https://example.com/terms/",
"title": "My API",
"version": "1.0.0"
},
"openapi": "3.1.0",
"paths": {
"/greeting/{name}": {
"get": {
"description": "Returns a greeting for the specified name.",
"operationId": "get-greeting",
"parameters": [
{
"description": "The name to greet",
"example": "world",
"in": "path",
"name": "name",
"required": true,
"schema": {
"description": "The name to greet",
"examples": [
"world"
],
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GreetingOutputBody"
}
}
},
"description": "Successful greeting response"
},
"400": {
"content": {
"application/json": {
"schema": {
"additionalProperties": false,
"properties": {
"error": {
"description": "Error message",
"examples": [
"Name too long"
],
"type": "string"
}
},
"required": [
"error"
],
"type": "object"
}
}
},
"description": "Invalid request parameters"
}
},
"summary": "Get a personalized greeting",
"tags": [
"Greetings"
]
}
}
},
"servers": [
{
"description": "Local development server",
"url": "http://localhost:8888"
}
],
"tags": [
{
"description": "Endpoints related to greeting messages",
"name": "greeting"
}
]
}
Copy Your Spec
Once the configuration is complete, you can generate the OpenAPI spec. Start your Go server by running:
go run main.go
To view your OpenAPI spec, navigate to the /openapi.json
endpoint on your server (e.g., http://localhost:8888/openapi.json).
Now that your openapi.json
file is generated, you're ready to proceed. Save your openapi.json
file to the liblab project directory you created during the initializing step. This is typically something like:
cd ../Huma-sdk
curl -o openapi.json http://localhost:8888/openapi.json
Configuring liblab
Now you'll need to make some minor updates to your liblab.config.json
file in your Huma-sdk folder:
- Point the
specFilePath
parameter to the location of your OpenAPI spec file (ex../openapi.json
). - Specify the
baseUrl
of your API. This is the URL that the SDK will use to make requests to your API.
The top of the file should then looks something like this:
{
"sdkName": "Huma-sdk",
"apiVersion": "1.0.0",
"apiName": "Huma-api",
"specFilePath": "./openapi.json",
"baseUrl": "http://localhost:PORT",
"languages": [
"go",
"java",
"python",
"typescript",
"csharp",
"php"
],
"auth": [
"bearer"
]
}
liblab's SDK generator supports many more advanced URL and environment configuration options than the basic configuration shown here.
Explore the configuration documentation to discover all the available settings and enhancements or review the SDK customization options for tailored adjustments.
Generate the SDK
During build you might see warnings about the OpenAPI spec. These are often minor issues that can be fixed later.
Now that you have an OpenAPI spec file and have finished setting the liblab.config.json
file, it's time to generate our SDK:
liblab build -y
The CLI will validate the OpenAPI spec and notify you about any issues with it or the liblab.config.json
.
The output will look something like this:
✓ No issues detected in the liblab config file.
No hooks found, SDKs will be generated without hooks.
⚠ Validation succeeded with warnings
Created /Users/username/projects/Huma-sdk/output/api-schema-validation.json with the full linting results
Next you'll see the builds started and once they're done you'll see a message like this:
Your SDKs are being generated. Visit the liblab portal (https://app.liblab.com/apis/Huma-sdk/builds/1234) to view more details on your build(s).
✓ C# built
✓ Go built
✓ Java built
✓ PHP built
✓ Python built
✓ TypeScript built
✓ Generate package-lock.json for TypeScript
Successfully generated SDKs for Python, Java, Go, TypeScript, C#, PHP. ♡ You can find them inside: /Users/username/projects/Huma-sdk/output
If we go inside the output
directory, we will see a directory for each of our SDKs:
ls output/
api-schema-validation.json go php typescript
csharp java python
Try out your SDK
The following instructions assume you have already set up the respective development environment for the language you are testing. If necessary refer to each language's official documentation before proceeding.
Learn more about the language versions liblab generated SDKs support.
The generated SDKs are intended to be deployed to package managers for end users. The instructions below to test your SDK locally will differ from user-facing instructions.
- TypeScript / JavaScript
- Python
- Java
- C#
cd output/typescript/examples
npm run setup
npm run start
cd output/python/examples
chmod +x install.sh
./install.sh
source .venv/bin/activate
python3 sample.py
cd output/java/example
chmod +x run.sh
./run.sh
cd output/csharp/Example
dotnet run --framework net9.0
Next Steps
Now that you've packaged your SDKs you can learn how to integrate them with your CI/CD pipeline and publish them to their respective package manager repositories. We currently have guides for: