Overview
Fiber provides powerful request validation through its binding system. By implementing a StructValidator, you can automatically validate incoming data from JSON, forms, query parameters, and more.
Setting Up Validation
Implementing a Validator
Fiber requires you to implement the StructValidator interface:
type StructValidator interface {
Validate ( out any ) error
}
Using go-playground/validator
The most popular choice is go-playground/validator:
import (
" github.com/gofiber/fiber/v3 "
" github.com/go-playground/validator/v10 "
)
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 (),
},
})
}
Validation Binding Methods
Validation works with all binding methods. The validator runs automatically after successful binding:
JSON Body
Query Parameters
Form Data
URI Parameters
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=120"`
}
app . Post ( "/users" , func ( c fiber . Ctx ) error {
user := new ( User )
if err := c . Bind (). Body ( user ); err != nil {
return c . Status ( 400 ). JSON ( fiber . Map { "error" : err . Error ()})
}
return c . JSON ( user )
})
Error Handling
Understanding BindError
Fiber wraps binding failures in BindError with source tracking:
type BindError struct {
Err error // underlying error
Source string // binding source: uri, query, body, header, cookie
Field string // struct field that failed
}
Manual Error Handling
By default, binding returns BindError for manual handling:
app . Post ( "/users" , func ( c fiber . Ctx ) error {
user := new ( User )
if err := c . Bind (). Body ( user ); err != nil {
var bindErr * fiber . BindError
if errors . As ( err , & bindErr ) {
// Handle based on source
switch bindErr . Source {
case fiber . BindSourceBody :
return c . Status ( 400 ). JSON ( fiber . Map {
"error" : "Invalid request body" ,
"field" : bindErr . Field ,
})
case fiber . BindSourceQuery :
return c . Status ( 400 ). JSON ( fiber . Map {
"error" : "Invalid query parameter" ,
"field" : bindErr . Field ,
})
}
}
return err
}
return c . JSON ( user )
})
Automatic Error Handling
Use WithAutoHandling() to automatically set HTTP 400 on errors:
app . Post ( "/users" , func ( c fiber . Ctx ) error {
user := new ( User )
// Automatically sets status 400 on validation errors
if err := c . Bind (). WithAutoHandling (). Body ( user ); err != nil {
return err // Status already set to 400
}
return c . JSON ( user )
})
Advanced Validation
Skipping Validation
You can skip validation when needed:
app . Post ( "/raw" , func ( c fiber . Ctx ) error {
user := new ( User )
// Skip validation for this request
if err := c . Bind (). SkipValidation ( true ). Body ( user ); err != nil {
return err
}
return c . JSON ( user )
})
Custom Validators
Implement custom validation logic:
type PasswordValidator struct {
validate * validator . Validate
}
func ( v * PasswordValidator ) Validate ( out any ) error {
// Run standard validation first
if err := v . validate . Struct ( out ); err != nil {
return err
}
// Add custom validation
if user , ok := out .( * User ); ok {
if ! isStrongPassword ( user . Password ) {
return errors . New ( "password must contain uppercase, lowercase, number, and special character" )
}
}
return nil
}
func isStrongPassword ( pwd string ) bool {
// Custom password strength logic
return len ( pwd ) >= 12 // Simplified example
}
Common validation tags from go-playground/validator:
type Product struct {
// Required fields
Name string `json:"name" validate:"required"`
// Length validation
Description string `json:"description" validate:"min=10,max=500"`
// Numeric ranges
Price float64 `json:"price" validate:"gt=0"`
Quantity int `json:"quantity" validate:"gte=0,lte=10000"`
// Format validation
Email string `json:"email" validate:"required,email"`
URL string `json:"url" validate:"omitempty,url"`
// Enumeration
Status string `json:"status" validate:"oneof=active inactive pending"`
// Cross-field validation
Password string `json:"password" validate:"required,min=8"`
PasswordConfirm string `json:"password_confirm" validate:"required,eqfield=Password"`
}
Binding from Multiple Sources
The All() method binds from multiple sources with precedence:
type RequestData struct {
// Can come from URL params, body, query, headers, or cookies
UserID string `uri:"userId" query:"user_id" validate:"required"`
Token string `header:"Authorization" cookie:"token"`
Page int `query:"page" validate:"gte=1"`
}
app . Get ( "/data/:userId" , func ( c fiber . Ctx ) error {
data := new ( RequestData )
// Binds in order: URI -> Body -> Query -> Headers -> Cookies
if err := c . Bind (). All ( data ); err != nil {
return c . Status ( 400 ). JSON ( fiber . Map { "error" : err . Error ()})
}
return c . JSON ( data )
})
Precedence Order : URI Params → Body → Query → Headers → CookiesValues from higher-precedence sources override lower-precedence ones.
Validation Behavior
Validation only runs for struct destinations (or pointers to structs). Binding into maps and other non-struct types skips validation automatically.
// Validation runs (struct)
type User struct {
Name string `validate:"required"`
}
user := new ( User )
c . Bind (). Body ( user ) // Validation runs
// Validation skipped (map)
data := make ( map [ string ] interface {})
c . Bind (). Body ( & data ) // No validation
Best Practices
Use descriptive validation tags - Make error messages clear
Handle errors appropriately - Return meaningful HTTP status codes
Validate at boundaries - Always validate external input
Use WithAutoHandling() for APIs - Simplifies error responses
Implement custom validators - For domain-specific rules
Test validation - Ensure validators catch invalid data
See Also