Request Binding
Binding decodes request data into a struct and validates it in a single call. Every Bind* function returns an error that is safe to return directly from a handler.
JSON body
BindJSON reads and decodes the request body, then validates the struct.
type CreatePostBody struct {
Title string `json:"title" validate:"required,min=1,max=200"`
Body string `json:"body" validate:"required"`
Priority string `json:"priority" validate:"required,oneof=low medium high"`
Draft *bool `json:"draft"` // pointer = optional field
}
func createPost(w http.ResponseWriter, r *http.Request) error {
var body CreatePostBody
if err := kori.BindJSON(r, &body); err != nil {
return err // 400 for invalid JSON, 422 for validation errors
}
// body.Title, body.Body, body.Priority, body.Draft
return kori.JSON(w, http.StatusCreated, post)
}curl -X POST http://localhost:8080/posts \
-H "Content-Type: application/json" \
-d '{"title":"Hello","body":"World","priority":"high"}'The default maximum body size is 4 MB. Larger bodies are rejected with 413 Payload Too Large. Override it at startup:
kori.SetMaxBodyBytes(16 << 20) // 16 MBQuery parameters
BindQuery decodes URL query parameters using query struct tags.
type ListPostsQuery struct {
Page int `query:"page" validate:"omitempty,min=1"`
PageSize int `query:"page_size" validate:"omitempty,min=1,max=100"`
Search string `query:"q"`
Tags []string `query:"tags"` // ?tags=go&tags=api or ?tags=go,api
}
func listPosts(w http.ResponseWriter, r *http.Request) error {
var q ListPostsQuery
if err := kori.BindQuery(r, &q); err != nil {
return err
}
// q.Page, q.PageSize, q.Search, q.Tags
return kori.JSON(w, http.StatusOK, posts)
}curl "http://localhost:8080/posts?page=2&page_size=20&q=kori&tags=go,api"Path parameters
BindPath decodes Chi URL parameters using path struct tags.
type PostParams struct {
ID string `path:"id" validate:"required,uuid4"`
}
func getPost(w http.ResponseWriter, r *http.Request) error {
var p PostParams
if err := kori.BindPath(r, &p); err != nil {
return err
}
// p.ID
return kori.JSON(w, http.StatusOK, post)
}TIP
For a single path param without validation, chi.URLParam(r, "id") is simpler.
Headers
BindHeader decodes HTTP headers using header struct tags.
type AuthHeaders struct {
Token string `header:"X-Auth-Token" validate:"required"`
APIVersion string `header:"X-Api-Version"`
}
func handler(w http.ResponseWriter, r *http.Request) error {
var h AuthHeaders
if err := kori.BindHeader(r, &h); err != nil {
return err
}
// h.Token, h.APIVersion
return kori.JSON(w, http.StatusOK, result)
}Combining sources
kori.Bind decodes path, query, and header parameters in a single call. Useful when a handler reads from multiple sources:
type GetPostParams struct {
ID string `path:"id"`
Expand string `query:"expand"`
Version string `header:"X-Api-Version"`
}
func getPost(w http.ResponseWriter, r *http.Request) error {
var p GetPostParams
if err := kori.Bind(r, &p); err != nil {
return err
}
// p.ID from path, p.Expand from query, p.Version from header
return kori.JSON(w, http.StatusOK, post)
}Form data
BindForm decodes application/x-www-form-urlencoded form values using form struct tags.
type ContactForm struct {
Name string `form:"name" validate:"required,min=2,max=100"`
Email string `form:"email" validate:"required,email"`
Message string `form:"message" validate:"required,min=10,max=1000"`
Subscribe bool `form:"subscribe"`
}
func handleContact(w http.ResponseWriter, r *http.Request) error {
var form ContactForm
if err := kori.BindForm(r, &form); err != nil {
return err
}
// form.Name, form.Email, form.Message, form.Subscribe
return kori.JSON(w, http.StatusOK, map[string]string{"status": "received"})
}Multipart forms and file uploads
BindMultipart decodes multipart/form-data. Non-file fields use form tags. File fields must be *multipart.FileHeader (single) or []*multipart.FileHeader (multiple).
type AvatarUpload struct {
DisplayName string `form:"display_name" validate:"required,min=2,max=50"`
Avatar *multipart.FileHeader `form:"avatar" validate:"required"`
}
func uploadAvatar(w http.ResponseWriter, r *http.Request) error {
var input AvatarUpload
if err := kori.BindMultipart(r, &input); err != nil {
return err
}
// open the uploaded file
f, err := input.Avatar.Open()
if err != nil {
return kori.InternalServerError("failed to open file")
}
defer f.Close()
return kori.JSON(w, http.StatusOK, map[string]string{
"filename": input.Avatar.Filename,
"name": input.DisplayName,
})
}Multiple file uploads:
type GalleryUpload struct {
Title string `form:"title" validate:"required"`
Photos []*multipart.FileHeader `form:"photos" validate:"required,min=1"`
}
func uploadGallery(w http.ResponseWriter, r *http.Request) error {
var input GalleryUpload
if err := kori.BindMultipart(r, &input); err != nil {
return err
}
// input.Photos is []*multipart.FileHeader
return kori.JSON(w, http.StatusCreated, map[string]int{
"uploaded": len(input.Photos),
})
}The default maximum memory for multipart parsing is 32 MB. Override it at startup:
kori.SetMaxMultipartMemory(64 << 20) // 64 MBSupported types
All Bind* functions support the following field types:
string, int / int8–64, uint / uint8–64, float32 / float64, bool, pointers to any of these, and []T slices (query and form only).