Skip to content

Validation

Kori uses go-playground/validator for struct validation. It runs automatically inside every Bind* call — you don't call the validator directly.

Validation tags

Add validate tags to any struct field:

go
type CreateUserBody struct {
    Name     string `json:"name"     validate:"required,min=2,max=100"`
    Email    string `json:"email"    validate:"required,email"`
    Age      int    `json:"age"      validate:"omitempty,gte=0,lte=120"`
    Role     string `json:"role"     validate:"required,oneof=admin editor viewer"`
    Password string `json:"password" validate:"required,min=8"`
    Website  string `json:"website"  validate:"omitempty,url"`
}

Common tags:

TagMeaning
requiredField must be present and non-zero
omitemptySkip validation if the field is empty
min=NMinimum length (string) or value (number)
max=NMaximum length (string) or value (number)
gte=NGreater than or equal to N
lte=NLess than or equal to N
gt=NGreater than N
lt=NLess than N
emailMust be a valid email address
urlMust be a valid URL
uuid4Must be a valid UUID v4
uuidMust be a valid UUID (any version)
oneof=a b cMust be one of the listed values
len=NMust have exactly N characters

The full list is in the validator documentation.

Binding and validation

Every Bind* call runs validation after decoding. No separate step is needed:

go
type CreateTodoBody struct {
    Title    string `json:"title"    validate:"required,min=1,max=200"`
    Priority string `json:"priority" validate:"required,oneof=low medium high"`
}

func createTodo(w http.ResponseWriter, r *http.Request) error {
    var body CreateTodoBody
    if err := kori.BindJSON(r, &body); err != nil {
        return err // handles both decoding and validation errors
    }
    // body is valid here
    return kori.JSON(w, http.StatusCreated, todo)
}

The same applies to all binding functions: BindQuery, BindPath, BindHeader, BindForm, BindMultipart.

Validation errors and 422 responses

When validation fails, Kori returns an *HTTPError with status 422 Unprocessable Entity and a details array describing each failing field:

json
{
  "message": "validation failed",
  "details": [
    {
      "field": "email",
      "tag": "email",
      "message": "email must be a valid email address"
    },
    {
      "field": "name",
      "tag": "min",
      "param": "2",
      "message": "name must be at least 2"
    }
  ]
}

Each detail entry is a kori.ValidationDetail:

go
type ValidationDetail struct {
    Field   string `json:"field"`
    Tag     string `json:"tag"`
    Param   string `json:"param,omitempty"`
    Message string `json:"message"`
}

Optional fields

Use a pointer or omitempty to make a field optional:

go
type UpdateTodoBody struct {
    Title     string `json:"title"     validate:"omitempty,min=1,max=200"`
    Completed *bool  `json:"completed"` // pointer: present = explicitly set
}
  • omitempty skips the validation rules when the field is zero-value
  • A pointer field allows the caller to distinguish between "not provided" and false

Field names in errors

Kori's validator uses the field's json, query, path, header, or form tag as the field name in error messages — not the Go struct field name. This means clients always see the name they sent.

go
type Query struct {
    Page int `query:"page" validate:"min=1"`
}
// error message: "page must be at least 1" (not "Page")