Handler Helpers
Humus provides handler helpers that simplify REST API development through type-safe request/response handling, automatic serialization, and OpenAPI schema generation. This quick reference shows how to use each helper function with complete, copy-paste-ready code examples.
JSON Handlers
HandleJson
Consume JSON request and produce JSON response - ideal for POST/PUT endpoints with request and response bodies.
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
handler := rest.HandlerFunc[CreateUserRequest, User](
func(ctx context.Context, req *CreateUserRequest) (*User, error) {
user := &User{
ID: generateID(),
Name: req.Name,
Email: req.Email,
}
return user, nil
},
)
rest.Handle(
http.MethodPost,
rest.BasePath("/users"),
rest.HandleJson(handler),
)
ProduceJson
Produce JSON response without consuming a request body - ideal for GET endpoints.
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
producer := rest.ProducerFunc[User](
func(ctx context.Context) (*User, error) {
// Extract path parameter
userID := rest.PathParamValue(ctx, "id")
user := getUserByID(ctx, userID)
return user, nil
},
)
rest.Handle(
http.MethodGet,
rest.BasePath("/users").Param("id"),
rest.ProduceJson(producer),
)
ConsumeOnlyJson
Consume JSON request without producing a response body - ideal for webhook endpoints.
type WebhookPayload struct {
Event string `json:"event"`
Repository string `json:"repository"`
}
consumer := rest.ConsumerFunc[WebhookPayload](
func(ctx context.Context, payload *WebhookPayload) error {
processWebhook(ctx, payload)
return nil
},
)
rest.Handle(
http.MethodPost,
rest.BasePath("/webhooks/github"),
rest.ConsumeOnlyJson(consumer),
)
ConsumeJson
Low-level helper that wraps a handler to add JSON request parsing.
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
handler := rest.HandlerFunc[CreateUserRequest, User](
func(ctx context.Context, req *CreateUserRequest) (*User, error) {
user := &User{
ID: generateID(),
Name: req.Name,
Email: req.Email,
}
return user, nil
},
)
// Wrap handler to add JSON request parsing
jsonHandler := rest.ConsumeJson(handler)
rest.Handle(
http.MethodPost,
rest.BasePath("/users"),
jsonHandler,
)
ReturnJson
Low-level helper that wraps a handler to add JSON response serialization.
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
handler := rest.HandlerFunc[CreateUserRequest, User](
func(ctx context.Context, req *CreateUserRequest) (*User, error) {
user := &User{
ID: generateID(),
Name: req.Name,
Email: req.Email,
}
return user, nil
},
)
// Wrap handler to add JSON response serialization
jsonHandler := rest.ReturnJson(handler)
rest.Handle(
http.MethodPost,
rest.BasePath("/users"),
jsonHandler,
)
Form Handlers
ConsumeForm
Wrap a handler to consume form-encoded request data.
type CreateUserRequest struct {
Name string `form:"name"`
Email string `form:"email"`
Age int `form:"age"`
}
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
handler := rest.HandlerFunc[CreateUserRequest, User](
func(ctx context.Context, req *CreateUserRequest) (*User, error) {
user := &User{
ID: generateID(),
Name: req.Name,
Email: req.Email,
Age: req.Age,
}
return user, nil
},
)
rest.Handle(
http.MethodPost,
rest.BasePath("/users"),
rest.ConsumeForm(handler),
)
ConsumeOnlyForm
Consume form-encoded data without producing a response body - ideal for form webhooks.
type WebhookForm struct {
Event string `form:"event"`
UserID string `form:"user_id"`
Action string `form:"action"`
}
consumer := rest.ConsumerFunc[WebhookForm](
func(ctx context.Context, form *WebhookForm) error {
processWebhookEvent(ctx, form.Event, form.UserID, form.Action)
return nil
},
)
rest.Handle(
http.MethodPost,
rest.BasePath("/webhooks/form"),
rest.ConsumeOnlyForm(consumer),
)
HandleForm
Consume form-encoded request and produce JSON response.
type SearchForm struct {
Query string `form:"q"`
Filter string `form:"filter"`
Page int `form:"page"`
}
type SearchResults struct {
Results []Result `json:"results"`
Total int `json:"total"`
Page int `json:"page"`
}
handler := rest.HandlerFunc[SearchForm, SearchResults](
func(ctx context.Context, form *SearchForm) (*SearchResults, error) {
results := performSearch(ctx, form.Query, form.Filter, form.Page)
return results, nil
},
)
rest.Handle(
http.MethodPost,
rest.BasePath("/search"),
rest.HandleForm(handler),
)
HTML Template Handlers
ProduceHTML
Produce HTML response using a template - ideal for GET endpoints rendering pages. Use this when your template for each request is independent of the request data. If you need to specify a response template dynamically, implement your own Handler which returns a rest.HTMLTemplateResponse with the Template field set based on the request.
import "html/template"
type User struct {
ID string
Name string
Email string
}
// Define template
tmpl := template.Must(template.New("user").Parse(`
<!DOCTYPE html>
<html>
<head><title>{{.Name}}'s Profile</title></head>
<body>
<h1>{{.Name}}</h1>
<p>Email: {{.Email}}</p>
</body>
</html>
`))
// Create producer
producer := rest.ProducerFunc[User](
func(ctx context.Context) (*User, error) {
userID := rest.PathParamValue(ctx, "id")
user := getUserFromDB(ctx, userID)
return user, nil
},
)
rest.Handle(
http.MethodGet,
rest.BasePath("/users").Param("id"),
rest.ProduceHTML(producer, tmpl),
)
ReturnHTML
Wrap a handler to return HTML responses using a template. Use this when your template for each request is independent of the request data. If you need to specify a response template dynamically, implement your own Handler which returns a rest.HTMLTemplateResponse with the Template field set based on the request.
import "html/template"
type SearchRequest struct {
Query string `json:"query"`
Filters string `json:"filters"`
}
type SearchResults struct {
Query string
Items []Item
}
// Define template
tmpl := template.Must(template.New("results").Parse(`
<div class="search-results">
<h2>Results for "{{.Query}}"</h2>
<ul>
{{range .Items}}
<li>{{.Title}} - {{.Description}}</li>
{{end}}
</ul>
</div>
`))
// Create handler
handler := rest.HandlerFunc[SearchRequest, SearchResults](
func(ctx context.Context, req *SearchRequest) (*SearchResults, error) {
results := performSearch(ctx, req.Query, req.Filters)
return results, nil
},
)
rest.Handle(
http.MethodPost,
rest.BasePath("/search"),
rest.ConsumeJson(rest.ReturnHTML(handler, tmpl)),
)
Core Interfaces
Handler
Core interface for handlers that consume a request and produce a response.
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// Implement Handler interface with a custom struct
type UserHandler struct {
store UserStore
}
func (h *UserHandler) Handle(ctx context.Context, req *CreateUserRequest) (*User, error) {
user := &User{
ID: generateID(),
Name: req.Name,
Email: req.Email,
}
if err := h.store.Create(ctx, user); err != nil {
return nil, err
}
return user, nil
}
// Use with HandleJson
handler := &UserHandler{store: myStore}
rest.Handle(
http.MethodPost,
rest.BasePath("/users"),
rest.HandleJson(handler),
)
Producer
Interface for producers that generate a response without consuming a request.
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// Implement Producer interface with a custom struct
type UserProducer struct {
store UserStore
}
func (p *UserProducer) Produce(ctx context.Context) (*User, error) {
userID := rest.PathParamValue(ctx, "id")
user, err := p.store.Get(ctx, userID)
if err != nil {
return nil, err
}
return user, nil
}
// Use with ProduceJson
producer := &UserProducer{store: myStore}
rest.Handle(
http.MethodGet,
rest.BasePath("/users").Param("id"),
rest.ProduceJson(producer),
)
Consumer
Interface for consumers that consume a request without producing a response.
type WebhookPayload struct {
Event string `json:"event"`
Repository string `json:"repository"`
}
// Implement Consumer interface with a custom struct
type WebhookConsumer struct {
processor WebhookProcessor
}
func (c *WebhookConsumer) Consume(ctx context.Context, payload *WebhookPayload) error {
return c.processor.Process(ctx, payload)
}
// Use with ConsumeOnlyJson
consumer := &WebhookConsumer{processor: myProcessor}
rest.Handle(
http.MethodPost,
rest.BasePath("/webhooks/github"),
rest.ConsumeOnlyJson(consumer),
)
Function Adapters
HandlerFunc
Function adapter that implements the Handler interface - use for inline handler definitions.
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// Define handler inline using HandlerFunc
handler := rest.HandlerFunc[CreateUserRequest, User](
func(ctx context.Context, req *CreateUserRequest) (*User, error) {
user := &User{
ID: generateID(),
Name: req.Name,
Email: req.Email,
}
return user, nil
},
)
rest.Handle(
http.MethodPost,
rest.BasePath("/users"),
rest.HandleJson(handler),
)
ProducerFunc
Function adapter that implements the Producer interface - use for inline producer definitions.
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// Define producer inline using ProducerFunc
producer := rest.ProducerFunc[User](
func(ctx context.Context) (*User, error) {
userID := rest.PathParamValue(ctx, "id")
user := getUserByID(ctx, userID)
return user, nil
},
)
rest.Handle(
http.MethodGet,
rest.BasePath("/users").Param("id"),
rest.ProduceJson(producer),
)
ConsumerFunc
Function adapter that implements the Consumer interface - use for inline consumer definitions.
type WebhookPayload struct {
Event string `json:"event"`
Repository string `json:"repository"`
}
// Define consumer inline using ConsumerFunc
consumer := rest.ConsumerFunc[WebhookPayload](
func(ctx context.Context, payload *WebhookPayload) error {
processWebhook(ctx, payload)
return nil
},
)
rest.Handle(
http.MethodPost,
rest.BasePath("/webhooks/github"),
rest.ConsumeOnlyJson(consumer),
)
Low-Level Building Blocks
ConsumeNothing
Low-level helper that wraps a Producer to create a handler that ignores the request body.
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// Create a producer
producer := rest.ProducerFunc[User](
func(ctx context.Context) (*User, error) {
userID := rest.PathParamValue(ctx, "id")
user := getUserByID(ctx, userID)
return user, nil
},
)
// Wrap producer to create a handler that consumes nothing
handler := rest.ConsumeNothing(producer)
rest.Handle(
http.MethodGet,
rest.BasePath("/users").Param("id"),
handler,
)
ProduceNothing
Low-level helper that wraps a Consumer to create a handler that produces no response body.
type WebhookPayload struct {
Event string `json:"event"`
Repository string `json:"repository"`
}
// Create a consumer
consumer := rest.ConsumerFunc[WebhookPayload](
func(ctx context.Context, payload *WebhookPayload) error {
processWebhook(ctx, payload)
return nil
},
)
// Wrap consumer to create a handler that produces nothing
handler := rest.ProduceNothing(consumer)
rest.Handle(
http.MethodPost,
rest.BasePath("/webhooks/github"),
handler,
)