Skip to main content
Version: Next

πŸ”Ž Validation

Validator package​

Fiber does not bundle a validation library. Instead, Bind accepts any validator you plug into fiber.Config.StructValidator and runs it automatically whenever request data is bound onto a struct. The core stays dependency-free, and validation becomes a one-time setup instead of per-handler boilerplate.

The steps below use go-playground/validator, the most common choice in the Go ecosystem, but any library works as long as you wrap it in the small adapter from step 2.

Step 1: Install a validator​

go get github.com/go-playground/validator/v10

Step 2: Wire it into the app config​

StructValidator expects a single Validate(out any) error method, so wrap the library in a tiny adapter and register it once:

import "github.com/go-playground/validator/v10"

type structValidator struct {
validate *validator.Validate
}

// Validator needs to implement the Validate method
func (v *structValidator) Validate(out any) error {
return v.validate.Struct(out)
}

// Set up your validator in the config
app := fiber.New(fiber.Config{
StructValidator: &structValidator{validate: validator.New()},
})
note

StructValidator runs only for struct destinations (or pointers to structs). Binding into maps and other non-struct types skips validation.

Step 3: Bind and validate in one call​

Tag your structs with the validator's rules. Every bind method (Body, Query, Form, and the rest) now triggers validation and returns its errors alongside binding errors:

type User struct {
Name string `json:"name" form:"name" query:"name" validate:"required"`
Age int `json:"age" form:"age" query:"age" validate:"gte=0,lte=100"`
}

app.Post("/", func(c fiber.Ctx) error {
user := new(User)

// Works with all bind methods: Body, Query, Form, ...
if err := c.Bind().Body(user); err != nil { // validation errors are returned here
return err
}

return c.JSON(user)
})

Returned as-is, a validation error surfaces through Fiber's error handling, so you can also shape it globally in a custom error handler. It is not wrapped in a fiber.BindError, which is reserved for binding failures, and a plain error carries no status, so the default error handler answers it with 500 Internal Server Error. Step 4 shows how to answer with a client error instead.

note

Validation runs only after binding succeeds, on the value you passed to the bind method. When parsing fails, the validator is not called. Bind().All validates once, on the struct merged from all sources.

To bypass the validator for part of a request, use SkipValidation. The setting stays in effect for the rest of the request until you turn validation back on with SkipValidation(false).

WithAutoHandling converts a validation error into 400 Bad Request only for Bind().All. The other bind methods return the validator's error unchanged.

Step 4: Shape the error response​

For field-level feedback, unwrap validator.ValidationErrors and answer with a structured body instead of the default text:

type User struct {
Name string `json:"name" validate:"required,min=3,max=32"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"gte=0,lte=100"`
Password string `json:"password" validate:"required,min=8"`
Website string `json:"website" validate:"url"`
}

app.Post("/user", func(c fiber.Ctx) error {
user := new(User)

if err := c.Bind().Body(user); err != nil {
var validationErrors validator.ValidationErrors
if errors.As(err, &validationErrors) {
out := make([]fiber.Map, 0, len(validationErrors))
for _, e := range validationErrors {
// e.Field() - field name, e.Tag() - failed rule,
// e.Param() - rule parameter, e.Value() - invalid value
out = append(out, fiber.Map{
"field": e.Field(),
"rule": e.Tag(),
})
}
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"errors": out})
}
return err
}

return c.JSON(user)
})

Step 5: Add your own rules​

Because the adapter is yours, you can layer custom checks on top of the tag rules:

// Custom validator for password strength
type PasswordValidator struct {
validate *validator.Validate
}

func (v *PasswordValidator) Validate(out any) error {
if err := v.validate.Struct(out); err != nil {
return err
}

// Custom password validation logic
if user, ok := out.(*User); ok {
if len(user.Password) < 8 {
return errors.New("password must be at least 8 characters")
}
// Add more password validation rules here
}

return nil
}

// Usage
app := fiber.New(fiber.Config{
StructValidator: &PasswordValidator{validate: validator.New()},
})