Handlers
Basic Handlers
A handler receives a *Ctx and returns an error:
app.Get("/hello", func(c *kruda.Ctx) error {
return c.Text("Hello, World!")
})The HandlerFunc type:
type HandlerFunc func(c *Ctx) errorTyped Handlers with C[T]
Typed handlers use Go generics to auto-parse request data into a struct:
type CreateUserInput struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=0,max=150"`
}
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
kruda.Post[CreateUserInput, User](app, "/users", func(c *kruda.C[CreateUserInput]) (*User, error) {
return &User{ID: "1", Name: c.In.Name, Email: c.In.Email}, nil
})The C[T] type extends Ctx with a typed In field:
type C[T any] struct {
*Ctx
In T // parsed input from request
}Typed Handler Registration
Package-level generic functions register typed handlers:
kruda.Get[In, Out](app, path, handler, opts...)
kruda.Post[In, Out](app, path, handler, opts...)
kruda.Put[In, Out](app, path, handler, opts...)
kruda.Delete[In, Out](app, path, handler, opts...)
kruda.Patch[In, Out](app, path, handler, opts...)Short variants (no error return, for prototyping):
kruda.GetX[In, Out](app, path, handler, opts...)
kruda.PostX[In, Out](app, path, handler, opts...)Group-level typed handlers:
kruda.GroupGet[In, Out](g, path, handler, opts...)
kruda.GroupPost[In, Out](g, path, handler, opts...)Struct Tags
Control how request data is bound to your struct:
type GetUserInput struct {
ID string `param:"id"` // from route parameter :id
Fields string `query:"fields"` // from query string ?fields=name,email
}
type UpdateUserInput struct {
ID string `param:"id"` // from route parameter
Name string `json:"name"` // from JSON body
Age int `json:"age"` // from JSON body
}Supported tag types:
| Tag | Source | Example |
|---|---|---|
param | Route parameters (:id) | param:"id" |
query | Query string (?key=val) | query:"fields" |
json | JSON request body | json:"name" |
header | Request headers | header:"X-Request-ID" |
Validation
After binding, validate struct tags are checked. Use them like this:
type Input struct {
Name string `json:"name" validate:"required,min=2,max=100"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=0,max=150"`
}Validation runs by default
Since v1.7.1 validate tags are enforced without any configuration. Opt out with kruda.New(kruda.WithoutValidation()), which parses input and checks nothing. kruda.WithValidator is for registering custom rules or messages, not for switching validation on.
Kruda implements 20 rules — (*kruda.Validator).RuleNames() lists them, plus any registered with Register. It also supports the two go-playground/validator modifiers that change how the rules around them apply: omitempty skips a field's rules when its value is the zero value, and dive applies every rule after it to each element of a slice, array or map rather than to the container. A dive failure names the element — tags[2], limits[free] — not just the field.
Bio string `validate:"omitempty,min=10"` // optional, but ≥10 chars if present
IDs []string `validate:"omitempty,dive,uuid"` // every element must be a UUIDThe tag syntax resembles go-playground/validator, which has far more rules, so a tag carried over from it may name one that does not exist here. Those are skipped with a startup warning naming the rule; the field's other rules still apply. The generated OpenAPI schema advertises the constraints regardless.
When validation fails, Kruda returns a structured error response:
{
"error": "Validation failed",
"code": 422,
"details": [
{ "field": "email", "message": "must be a valid email" }
]
}Response Methods
Handlers use Ctx methods to send responses:
// JSON response (status set via Status())
c.Status(200).JSON(user)
c.JSON(user) // default status 200
// Text response
c.Status(200).Text("OK")
c.Text("OK")
// HTML response
c.HTML("<h1>Hello</h1>")
// No content (204)
c.NoContent()
// With headers (method chaining)
c.Status(200).SetHeader("X-Custom", "value").JSON(data)
// Redirect (default 302)
c.Redirect("/new-location")
c.Redirect("/new-location", 301)See Context API for all response methods.
Route Options
Add metadata for OpenAPI generation:
kruda.Post[CreateUserInput, User](app, "/users", handler,
kruda.WithDescription("Create a new user"),
kruda.WithTags("users"),
)