Claude-skill-registry huma-endpoint
Guide for creating Huma API endpoints following this project's conventions including routing, input/output structs, error handling, and OpenAPI documentation.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/huma-endpoint" ~/.claude/skills/majiayu000-claude-skill-registry-huma-endpoint && rm -rf "$T"
manifest:
skills/data/huma-endpoint/SKILL.mdsource content
Huma Endpoint Creation
Use this skill when creating new API endpoints for this Huma REST API application.
For comprehensive coding guidelines, see
AGENTS.md in the repository root.
Router Setup
Create route handlers in
internal/http/v1/ and register them in routes.go:
// internal/http/v1/routes/routes.go func Register(api huma.API) { hello.Register(api) items.Register(api) // Add new routes here }
Note: The health endpoint is a plain HTTP handler registered at the root level in
main.go, not via Huma.
Output Struct Pattern
Use plain structs with a
Body field for the response payload:
import "github.com/janisto/huma-playground/internal/platform/timeutil" // ResourceData models the response payload. type ResourceData struct { ID string `json:"id" doc:"Unique identifier" example:"res-001"` Name string `json:"name" doc:"Display name" example:"My Resource"` CreatedAt timeutil.Time `json:"createdAt" doc:"Creation timestamp" example:"2024-01-15T10:30:00.000Z"` } // ResourceOutput is the response wrapper. type ResourceOutput struct { Body ResourceData }
Input Struct Pattern
Always prefix input types with the resource name for consistency:
// Path parameters type ResourceGetInput struct { ID string `path:"id" doc:"Resource identifier" example:"res-001"` } // Query parameters type ResourceListInput struct { Status string `query:"status" doc:"Filter by status" example:"active" enum:"active,inactive"` Limit int `query:"limit" doc:"Maximum items" example:"10" minimum:"1" maximum:"100"` } // Request body type ResourceCreateInput struct { Body struct { Name string `json:"name" doc:"Resource name" example:"New Resource" minLength:"1" maxLength:"100"` } }
GET Endpoint
Simple retrieval endpoints use
huma.Get:
func registerResource(api huma.API) { huma.Get(api, "/resources/{id}", func(ctx context.Context, input *ResourceGetInput) (*ResourceOutput, error) { resource, err := getResource(input.ID) if err != nil { return nil, huma.Error404NotFound("resource not found") } return &ResourceOutput{Body: resource}, nil }) }
POST Endpoint with 201 Created
Use
huma.Register for operations requiring custom configuration:
type CreateResourceOutput struct { Location string `header:"Location" doc:"URL of created resource"` Body ResourceData } huma.Register(api, huma.Operation{ OperationID: "create-resource", Method: http.MethodPost, Path: "/resources", Summary: "Create a new resource", Description: "Creates a new resource and returns its data.", DefaultStatus: http.StatusCreated, Tags: []string{"Resources"}, }, func(ctx context.Context, input *ResourceCreateInput) (*CreateResourceOutput, error) { resource := createResource(input.Body.Name) return &CreateResourceOutput{ Location: fmt.Sprintf("/resources/%s", resource.ID), Body: resource, }, nil })
PUT/PATCH Endpoint
type UpdateResourceInput struct { ID string `path:"id"` Body struct { Name string `json:"name" doc:"Updated name" minLength:"1" maxLength:"100"` } } huma.Register(api, huma.Operation{ OperationID: "update-resource", Method: http.MethodPut, Path: "/resources/{id}", Summary: "Update a resource", Tags: []string{"Resources"}, }, func(ctx context.Context, input *UpdateResourceInput) (*ResourceOutput, error) { resource, err := updateResource(input.ID, input.Body.Name) if err != nil { return nil, huma.Error404NotFound("resource not found") } return &ResourceOutput{Body: resource}, nil })
DELETE Endpoint
Return 204 No Content for successful deletions:
huma.Register(api, huma.Operation{ OperationID: "delete-resource", Method: http.MethodDelete, Path: "/resources/{id}", Summary: "Delete a resource", DefaultStatus: http.StatusNoContent, Tags: []string{"Resources"}, }, func(ctx context.Context, input *ResourceInput) (*struct{}, error) { if err := deleteResource(input.ID); err != nil { return nil, huma.Error404NotFound("resource not found") } return nil, nil })
Error Handling
Use Huma's built-in error helpers for RFC 9457 Problem Details:
// Common error responses huma.Error400BadRequest("invalid request") huma.Error403Forbidden("access denied") huma.Error404NotFound("resource not found") huma.Error422UnprocessableEntity("validation failed", fieldErrors...) huma.Error500InternalServerError("internal error") // Custom status codes huma.NewError(http.StatusTeapot, "custom message") huma.NewError(http.StatusConflict, "resource already exists")
Logging
Use context-aware logging helpers:
import ( "go.uber.org/zap" applog "github.com/janisto/huma-playground/internal/platform/logging" ) func handler(ctx context.Context, input *Input) (*Output, error) { applog.LogInfo(ctx, "processing request", zap.String("id", input.ID)) if err != nil { applog.LogError(ctx, "operation failed", err, zap.String("id", input.ID)) return nil, huma.Error500InternalServerError("operation failed") } return &Output{Body: result}, nil }
Field Documentation
Every field MUST have:
tag for OpenAPI descriptiondoc:
tag for OpenAPI examplesexample:
Validation tags:
,minLength
for stringsmaxLength
,minimum
for numbersmaximum
for allowed valuesenum:
for regex validationpattern:
Status Code Reference
| Method | Success Status | Use Case |
|---|---|---|
| GET | 200 OK | Retrieve resource(s) |
| POST | 201 Created | Create resource (include Location header) |
| PUT | 200 OK | Replace resource |
| PATCH | 200 OK | Partial update |
| DELETE | 204 No Content | Remove resource |
Error Status Codes
| Status | Use Case |
|---|---|
| 400 | Malformed syntax, invalid cursor |
| 401 | Missing authentication |
| 403 | Authenticated but not authorized |
| 404 | Resource not found |
| 409 | Conflict (duplicate resource) |
| 422 | Validation failures |
| 500 | Unexpected server error |