Skip to content

Handlers

Basic Handlers

A handler receives a *Ctx and returns an error:

go
app.Get("/hello", func(c *kruda.Ctx) error {
    return c.Text("Hello, World!")
})

The HandlerFunc type:

go
type HandlerFunc func(c *Ctx) error

Typed Handlers with C[T]

Typed handlers use Go generics to auto-parse request data into a struct:

go
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:

go
type C[T any] struct {
    *Ctx
    In T // parsed input from request
}

Typed Handler Registration

Package-level generic functions register typed handlers:

go
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):

go
kruda.GetX[In, Out](app, path, handler, opts...)
kruda.PostX[In, Out](app, path, handler, opts...)

Group-level typed handlers:

go
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:

go
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:

TagSourceExample
paramRoute parameters (:id)param:"id"
queryQuery string (?key=val)query:"fields"
jsonJSON request bodyjson:"name"
headerRequest headersheader:"X-Request-ID"

Validation

After binding, validate struct tags are checked. Use them like this:

go
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.

go
Bio  string   `validate:"omitempty,min=10"`  // optional, but ≥10 chars if present
IDs  []string `validate:"omitempty,dive,uuid"` // every element must be a UUID

The 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:

json
{
  "error": "Validation failed",
  "code": 422,
  "details": [
    { "field": "email", "message": "must be a valid email" }
  ]
}

Response Methods

Handlers use Ctx methods to send responses:

go
// 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:

go
kruda.Post[CreateUserInput, User](app, "/users", handler,
    kruda.WithDescription("Create a new user"),
    kruda.WithTags("users"),
)

Released under the MIT License.