Skip to content
Requests & Responses

Requests & Responses

The request package turns HTTP requests into typed Go structs and back into JSON responses. request.Handler builds an http.Handler from a function that takes a request struct and returns a response.

Defining a handler

type CreateUserRequest struct {
	Name  string `json:"name" validate:"required|min:3"`
	Email string `json:"email" validate:"required|email"`
}

type CreateUserResponse struct {
	ID int `json:"id"`
}

userCreate := request.Handler(func(r *CreateUserRequest) (*CreateUserResponse, error) {
	user, err := model.Save(tx, &User{Name: r.Name, Email: r.Email})
	if err != nil {
		return nil, err
	}
	return &CreateUserResponse{ID: user.ID}, nil
})

Populating the request

Run reads the request into the struct. Every exported field that should be populated must carry one of these tags, or the handler’s Validate fails at startup:

tagsource
jsonthe JSON body field, or a form field when the content type is form-encoded or multipart/form-data
querya query string parameter
patha gorilla/mux path variable ({name} in the route)
injecta dependency from the DI container instead of request data

Supported types are the built-in numerics, string, bool, and slices of those, plus types (or pointers to types) that implement encoding.TextUnmarshaler. Query and path values are decoded with the same logic; JSON bodies are decoded with encoding/json, where a pointer field requested as null stays nil. A field of type fs.File is populated from a multipart/form-data upload with a matching json tag.

Fields with an inject tag are filled from the dependency provider. A model field marked inject:"id" is loaded by its primary key from the route or query, as described in the models and DI pages.

Responses

The handler’s return value is converted to a response with respond:

  • a Responder (an http.Handler too, or *http.Response) is served as-is;
  • an http.Handler is invoked directly;
  • anything else is encoded as JSON with NewJSONResponse.

NewResponse(body) builds a Responder from an io.Reader with a chainable SetStatus/AddHeader builder, NewHTMLResponse wraps it with a text/html content type, and NewJSONResponse(data) sets the application/json content type with indent.

Errors

Errors returned from a handler become responses. A ValidationError (a map[string][]string of field errors) is turned into a 422 response via NewHTTPError. The StatusError constants provide HTTP statuses as errors, for example request.ErrStatusNotFound or request.ErrStatusUnauthorized.

HTTPError wraps any error with a status; ErrorHandler dispatches an error to a Responder if it implements one, and falls back to a 500 otherwise. An HTMLError provides custom HTML for the error page.

Middleware

HandleErrors recovers panics and collects errors so a failing handler produces one response through ErrorHandler; custom error handlers can be passed to pick the response:

r.Use(request.HandleErrors(func(ctx context.Context, err error) http.Handler {
	if errors.Is(err, request.ErrStatusNotFound) {
		return view.View("404.html", nil)
	}
	return nil
}))

DIMiddleware puts the current *http.Request and http.ResponseWriter in the request context so they can be injected. The kernel adds it automatically, and request.Register(ctx) registers both as dependencies during bootstrap.

Validation

After the struct is populated it is validated, and failure returns a ValidationError. Rules come from the validate tag separated by |, with : for arguments. required is handled specially, and the common rules are:

ruleapplies to
min, maxnumbers, strings, and arrays
gt, gte, lt, ltenumbers and strings
multiple_ofnumbers
email, url, uuid, ip_address, regexstrings
alpha, numeric, alpha_num, alpha_dashstrings
starts_with, ends_with, length, length_between, in, not_instrings
accepted, declinedbooleans
after, after_or_equal, before, before_or_equaltimes

The validate package defines the validate.Validator interface and the validate.Append helper used by the kernel, router, and DI validator to collect startup validation errors.

OpenAPI

RequestHandler implements openapidoc.Operationer. Operation derives query and path parameters, the body schema, and the default response schema from the request and response types, and a custom operation can be supplied with Docs(*spec.OperationProps).

Internal helpers

Run(req, requestStruct) is exported for embedding request handling in custom handler types, as are Respond, RespondError, and the File/FileInfo types that expose an uploaded file through the io/fs interfaces.