Learning Fiber - Binding, Extractors & Validation
Episode 5 of 23

Learning Fiber - Binding, Extractors & Validation

This episode covers input handling in Fiber v3: binding body, query, URI, and header to structs with json, form, and query tags; the extractor package for fetching values declaratively with fallbacks; and input validation with go-playground/validator.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

In episode 4 you learned to read input manually via c.Params and c.Query. Episode 5 goes one level up: binding, extractors, and validation — mapping input to structs, extracting values declaratively, and making sure data is valid before it's processed.

Binding to Structs

Body Binding: JSON and Form

The most common way: bind a JSON body to a struct with the json tag. Fiber also recognizes form, xml, and msgpack according to Content-Type:

Binding body ke struct
type User struct {
    Name string `json:"name" form:"name"`
    Age  int    `json:"age" form:"age"`
}
 
app.Post("/users", func(c fiber.Ctx) error {
    var user User
    if err := c.Bind().Body(&user); err != nil {
        return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
            "error": err.Error(),
        })
    }
    return c.JSON(user)
})

c.Bind().Body(&user) reads the body, selects the parser based on Content-Type, and fills the struct. If the payload is JSON, the json tags are used; if it's a form, the form tags. Both can be written together as in the example above.

Query, URI, and Header Binding

Binding isn't limited to the body. Other sources can also be bound to the same struct:

Binding dari berbagai sumber
type Filter struct {
    Search string   `query:"search"`
    Tags   []string `query:"tags"`
}
 
type ProductParam struct {
    ID uint `uri:"id"`
}
 
app.Get("/products", func(c fiber.Ctx) error {
    var f Filter
    if err := c.Bind().Query(&f); err != nil {
        return err
    }
    return c.JSON(f)
})
 
app.Get("/products/:id", func(c fiber.Ctx) error {
    var p ProductParam
    if err := c.Bind().URI(&p); err != nil {
        return err
    }
    return c.JSON(p)
})

c.Bind().Query(&f) maps query parameters to query tags, and c.Bind().URI(&p) maps path parameters to uri tags. For headers, c.Bind().Header(...) is available with the header tag, and c.Bind().Cookie(...) for cookies. If you want all sources at once, c.Bind().All(&struct) follows the order URI, body, query, header, then cookie.

The Extractors Package

Declarative Extraction with Fallbacks

The github.com/gofiber/fiber/v3/extractors package provides extractor functions that can be chained. Middleware like session and JWT use it, but you can also use it for your own needs:

Extractor dengan fallback
import "github.com/gofiber/fiber/v3/extractors"
 
apiKey := extractors.Chain(
    extractors.FromHeader("X-API-Key"),
    extractors.FromQuery("api_key"),
    extractors.FromCookie("api_key"),
)
 
app.Use(func(c fiber.Ctx) error {
    key, err := apiKey.Extract(c)
    if err != nil {
        return c.Status(fiber.StatusUnauthorized).SendString("API key dibutuhkan")
    }
    c.Locals("api_key", key)
    return c.Next()
})

extractors.Chain(...) tries the X-API-Key header first, then the api_key query, then the api_key cookie. If none are present, .Extract(c) returns an error. This pattern makes extraction logic reusable and usable in many middleware.

Validation with go-playground/validator

Setting Up StructValidator

Fiber doesn't reinvent validators. It uses the StructValidator interface and integrates it with go-playground/validator:

Setup validator di config
import (
    "github.com/go-playground/validator/v10"
    "github.com/gofiber/fiber/v3"
)
 
type structValidator struct {
    validate *validator.Validate
}
 
func (v *structValidator) Validate(out any) error {
    return v.validate.Struct(out)
}
 
func main() {
    app := fiber.New(fiber.Config{
        StructValidator: &structValidator{validate: validator.New()},
    })
}

With fiber.Config{StructValidator: ...}, every binding automatically runs validation if the struct uses validate tags.

Validation Tags on Structs

Struct dengan tag validasi
type RegisterRequest struct {
    Email    string `json:"email" validate:"required,email"`
    Password string `json:"password" validate:"required,min=8"`
    Age      int    `json:"age" validate:"gte=18,lte=99"`
}
 
app.Post("/register", func(c fiber.Ctx) error {
    var req RegisterRequest
    if err := c.Bind().JSON(&req); err != nil {
        return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
            "error": err.Error(),
        })
    }
    return c.Status(fiber.StatusCreated).JSON(req)
})

validate:"required,email" ensures the field is required and email-shaped; min=8 requires a password of at least 8 characters; gte=18,lte=99 limits the age range. c.Bind().JSON(&req) runs automatic validation and returns an error if any field violates the rules.

Handling Binding Errors

BindError and Auto Handling

By default, binding errors can be inspected with errors.As to get details about the failure source:

Membedakan sumber error
var req struct {
    ID   int    `uri:"id"`
    Name string `json:"name"`
}
 
if err := c.Bind().All(&req); err != nil {
    var be *fiber.BindError
    if errors.As(err, &be) && be.Source == fiber.BindSourceURI {
        return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
            "error": "resource tidak ditemukan",
        })
    }
    return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
        "error": "request tidak valid",
    })
}

*fiber.BindError contains Source and Field that describe where the failure came from. There's also WithAutoHandling() which automatically returns status 400 — suitable when you don't need detailed control. Note: validation errors aren't wrapped in BindError, so use errors.As to distinguish them.

Testing

Test binding dan validasi
curl -X POST -H "Content-Type: application/json" --data '{"name":"Budi","age":30}' http://localhost:3000/users
curl -X POST -H "Content-Type: application/json" \
  --data '{"email":"budi@example.com","password":"rahasia123","age":30}'   http://localhost:3000/register

Closing

Key takeaways:

  • c.Bind().Body(), .Query(), .URI(), .Header(), and .Cookie() map input to structs.
  • Tags determine the source: json, form, query, uri, header, cookie.
  • c.Bind().All() combines all sources in the order URI, body, query, header, cookie.
  • The extractors package provides declarative extraction with Chain and fallbacks.
  • Validation uses fiber.Config{StructValidator} with validate tags.
  • *fiber.BindError helps distinguish the source of binding errors.

In the next episode, episode 6, we discuss middleware and hooks — writing your own middleware with app.Use, understanding sequential execution and c.Next(), and using v3 lifecycle hooks like OnListen, OnPreShutdown, and OnPostShutdown.

Learning Fiber - Binding, Extractors & Validation | Learn Fiber