Skip to content

Response Transformers#

Router middleware operates on router-specific request & response objects whose bodies are []byte slices or streams. Huma operations operate on specific struct instances. Sometimes there is a need to generically operate on structured response data after the operation handler has run but before the response is serialized to bytes. This is where response transformers come in.

flowchart LR
	Request --> Middleware
	Middleware --> Unmarshal
	subgraph Huma
		Unmarshal --> Handler
		Handler --> Transformer
		Transformer --> Marshal
	Marshal --> Response

	style Transformer stroke:#f9f,stroke-width:2px,stroke-dasharray: 5 5

Response transformers enable you to modify the response on the fly. For example, you could add a Link header to the response to indicate that the response body is described by a JSON Schema. This is done by implementing the huma.Transformer interface and registering it with the API.

A simple for-fun example might look something like this:

// FieldSelectTransform is an example of a transform that can use an input
// header value to modify the response on the server, providing a GraphQL-like
// way to send only the fields that the client wants over the wire.
func FieldSelectTransform(ctx Context, status string, v any) (any, error) {
	if fields := ctx.Header("Fields"); fields != "" {
		// Ugh this is inefficient... consider other ways of doing this :-(
		var tmp any
		b, _ := json.Marshal(v)
		json.Unmarshal(b, &tmp)
		result, _, err := shorthand.GetPath(fields, tmp, shorthand.GetOptions{})
		return result, err
	return v, nil

This could be used like so:

$ restish -H 'Fields: {id, tag_names: tags[].name}'

See the huma.SchemaLinkTransformer for a more real-world in-depth example.

Dive Deeper#