fiber/docs/api/bind.md

16 KiB

id title description sidebar_position toc_max_heading_level
bind 📎 Bind Binds the request and response items to a struct. 4 4

Bindings are used to parse the request/response body, query parameters, cookies and much more into a struct.

:::info

All binder returned value are only valid within the handler. Do not store any references.
Make copies or use the Immutable setting instead. Read more...

:::

Binders

Body

Binds the request body to a struct.

It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a JSON body with a field called Pass, you would use a struct field of json:"pass".

content-type struct tag
application/x-www-form-urlencoded form
multipart/form-data form
application/json json
application/xml xml
text/xml xml
func (b *Bind) Body(out any) error
// Field names should start with an uppercase letter
type Person struct {
    Name string `json:"name" xml:"name" form:"name"`
    Pass string `json:"pass" xml:"pass" form:"pass"`
}

app.Post("/", func(c fiber.Ctx) error {
    p := new(Person)
    
    if err := c.Bind().Body(p); err != nil {
        return err
    }
    
    log.Println(p.Name) // john
    log.Println(p.Pass) // doe
    
    // ...
})

// Run tests with the following curl commands

// curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"pass\":\"doe\"}" localhost:3000

// curl -X POST -H "Content-Type: application/xml" --data "<login><name>john</name><pass>doe</pass></login>" localhost:3000

// curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "name=john&pass=doe" localhost:3000

// curl -X POST -F name=john -F pass=doe http://localhost:3000

// curl -X POST "http://localhost:3000/?name=john&pass=doe"

The methods for the various bodies can also be used directly:

Form

Binds the request form body to a struct.

It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a Form body with a field called Pass, you would use a struct field of form:"pass".

func (b *Bind) Form(out any) error
// Field names should start with an uppercase letter
type Person struct {
    Name string `form:"name"`
    Pass string `form:"pass"`
}

app.Post("/", func(c fiber.Ctx) error {
    p := new(Person)
    
    if err := c.Bind().Form(p); err != nil {
        return err
    }
    
    log.Println(p.Name) // john
    log.Println(p.Pass) // doe
    
    // ...
})

// Run tests with the following curl commands

// curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "name=john&pass=doe" localhost:3000

JSON

Binds the request json body to a struct.

It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a JSON body with a field called Pass, you would use a struct field of json:"pass".

func (b *Bind) JSON(out any) error
// Field names should start with an uppercase letter
type Person struct {
    Name string `json:"name"`
    Pass string `json:"pass"`
}

app.Post("/", func(c fiber.Ctx) error {
    p := new(Person)
    
    if err := c.Bind().JSON(p); err != nil {
        return err
    }

    log.Println(p.Name) // john
    log.Println(p.Pass) // doe
    
    // ...
})

// Run tests with the following curl commands

// curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"pass\":\"doe\"}" localhost:3000

MultipartForm

Binds the request multipart form body to a struct.

It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a MultipartForm body with a field called Pass, you would use a struct field of form:"pass".

func (b *Bind) MultipartForm(out any) error
// Field names should start with an uppercase letter
type Person struct {
    Name string `form:"name"`
    Pass string `form:"pass"`
}

app.Post("/", func(c fiber.Ctx) error {
    p := new(Person)
    
    if err := c.Bind().MultipartForm(p); err != nil {
        return err
    }
    
    log.Println(p.Name) // john
    log.Println(p.Pass) // doe
    
    // ...
})

// Run tests with the following curl commands

// curl -X POST -H "Content-Type: multipart/form-data" -F "name=john" -F "pass=doe" localhost:3000

XML

Binds the request xml form body to a struct.

It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse an XML body with a field called Pass, you would use a struct field of xml:"pass".

func (b *Bind) XML(out any) error
// Field names should start with an uppercase letter
type Person struct {
    Name string `xml:"name"`
    Pass string `xml:"pass"`
}

app.Post("/", func(c fiber.Ctx) error {
    p := new(Person)
    
    if err := c.Bind().XML(p); err != nil {
        return err
    }
    
    log.Println(p.Name) // john
    log.Println(p.Pass) // doe
    
    // ...
})

// Run tests with the following curl commands

// curl -X POST -H "Content-Type: application/xml" --data "<login><name>john</name><pass>doe</pass></login>" localhost:3000

This method is similar to Body-Binding, but for cookie parameters. It is important to use the struct tag "cookie". For example, if you want to parse a cookie with a field called Age, you would use a struct field of cookie:"age".

func (b *Bind) Cookie(out any) error
// Field names should start with an uppercase letter
type Person struct {
    Name     string  `cookie:"name"`
    Age      int     `cookie:"age"`
    Job      bool    `cookie:"job"`
}

app.Get("/", func(c fiber.Ctx) error {
    p := new(Person)
    
    if err := c.Bind().Cookie(p); err != nil {
        return err
    }
    
    log.Println(p.Name)     // Joseph
    log.Println(p.Age)      // 23
    log.Println(p.Job)      // true
})
// Run tests with the following curl command
// curl.exe --cookie "name=Joseph; age=23; job=true" http://localhost:8000/

Header

This method is similar to Body-Binding, but for request headers. It is important to use the struct tag "header". For example, if you want to parse a request header with a field called Pass, you would use a struct field of header:"pass".

func (b *Bind) Header(out any) error
// Field names should start with an uppercase letter
type Person struct {
    Name     string     `header:"name"`
    Pass     string     `header:"pass"`
    Products []string   `header:"products"`
}

app.Get("/", func(c fiber.Ctx) error {
    p := new(Person)
    
    if err := c.Bind().Header(p); err != nil {
        return err
    }
    
    log.Println(p.Name)     // john
    log.Println(p.Pass)     // doe
    log.Println(p.Products) // [shoe, hat]
    
    // ...
})
// Run tests with the following curl command

// curl "http://localhost:3000/" -H "name: john" -H "pass: doe" -H "products: shoe,hat"

Query

This method is similar to Body-Binding, but for query parameters. It is important to use the struct tag "query". For example, if you want to parse a query parameter with a field called Pass, you would use a struct field of query:"pass".

func (b *Bind) Query(out any) error
// Field names should start with an uppercase letter
type Person struct {
    Name     string     `query:"name"`
    Pass     string     `query:"pass"`
    Products []string   `query:"products"`
}

app.Get("/", func(c fiber.Ctx) error {
    p := new(Person)
    
    if err := c.Bind().Query(p); err != nil {
        return err
    }
    
    log.Println(p.Name)        // john
    log.Println(p.Pass)        // doe
    // fiber.Config{EnableSplittingOnParsers: false} - default
    log.Println(p.Products)    // ["shoe,hat"]
    // fiber.Config{EnableSplittingOnParsers: true}
    // log.Println(p.Products) // ["shoe", "hat"]
    
    
    // ...
})
// Run tests with the following curl command

// curl "http://localhost:3000/?name=john&pass=doe&products=shoe,hat"

:::info For more parser settings please look here Config :::

RespHeader

This method is similar to Body-Binding, but for response headers. It is important to use the struct tag "respHeader". For example, if you want to parse a request header with a field called Pass, you would use a struct field of respHeader:"pass".

func (b *Bind) Header(out any) error
// Field names should start with an uppercase letter
type Person struct {
    Name     string     `respHeader:"name"`
    Pass     string     `respHeader:"pass"`
    Products []string   `respHeader:"products"`
}

app.Get("/", func(c fiber.Ctx) error {
    p := new(Person)
    
    if err := c.Bind().RespHeader(p); err != nil {
        return err
    }
    
    log.Println(p.Name)     // john
    log.Println(p.Pass)     // doe
    log.Println(p.Products) // [shoe, hat]
    
    // ...
})
// Run tests with the following curl command

// curl "http://localhost:3000/" -H "name: john" -H "pass: doe" -H "products: shoe,hat"

URI

This method is similar to Body-Binding, but for path parameters. It is important to use the struct tag "uri". For example, if you want to parse a path parameter with a field called Pass, you would use a struct field of uri:"pass"

func (b *Bind) URI(out any) error
// GET http://example.com/user/111
app.Get("/user/:id", func(c fiber.Ctx) error {
    param := struct {ID uint `uri:"id"`}{}
    
    c.Bind().URI(&param) // "{"id": 111}"
    
    // ...
})

Custom

To use custom binders, you have to use this method.

You can register them from RegisterCustomBinder method of Fiber instance.

func (b *Bind) Custom(name string, dest any) error
app := fiber.New()

// My custom binder
customBinder := &customBinder{}
// Name of custom binder, which will be used as Bind().Custom("name")
func (*customBinder) Name() string {
    return "custom"
}
// Is used in the Body Bind method to check if the binder should be used for custom mime types
func (*customBinder) MIMETypes() []string {
    return []string{"application/yaml"}
}
// Parse the body and bind it to the out interface
func (*customBinder) Parse(c Ctx, out any) error {
    // parse yaml body
    return yaml.Unmarshal(c.Body(), out)
}
// Register custom binder
app.RegisterCustomBinder(customBinder)

// curl -X POST http://localhost:3000/custom -H "Content-Type: application/yaml" -d "name: John"
app.Post("/custom", func(c Ctx) error {
    var user User
    // output: {Name:John}
    // Custom binder is used by the name
    if err := c.Bind().Custom("custom", &user); err != nil {
        return err
    }
    // ...
    return c.JSON(user)
})

Internally they are also used in the Body method. For this the MIMETypes method is used to check if the custom binder should be used for the given content type.

Options

For more control over the error handling, you can use the following methods.

Must

If you want to handle binder errors automatically, you can use Must. If there's an error it'll return error and 400 as HTTP status.

func (b *Bind) Must() *Bind

Should

To handle binder errors manually, you can prefer Should method. It's default behavior of binder.

func (b *Bind) Should() *Bind

SetParserDecoder

Allow you to config BodyParser/QueryParser decoder, base on schema's options, providing possibility to add custom type for parsing.

func SetParserDecoder(parserConfig fiber.ParserConfig{
    IgnoreUnknownKeys bool,
    ParserType        []fiber.ParserType{
        Customtype any,
        Converter  func(string) reflect.Value,
    },
    ZeroEmpty         bool,
    SetAliasTag       string,
})

type CustomTime time.Time

// String() returns the time in string
func (ct *CustomTime) String() string {
    t := time.Time(*ct).String()
        return t
    }
    
    // Register the converter for CustomTime type format as 2006-01-02
    var timeConverter = func(value string) reflect.Value {
    fmt.Println("timeConverter", value)
    if v, err := time.Parse("2006-01-02", value); err == nil {
        return reflect.ValueOf(v)
    }
    return reflect.Value{}
}

customTime := fiber.ParserType{
    Customtype: CustomTime{},
    Converter:  timeConverter,
}

// Add setting to the Decoder
fiber.SetParserDecoder(fiber.ParserConfig{
    IgnoreUnknownKeys: true,
    ParserType:        []fiber.ParserType{customTime},
    ZeroEmpty:         true,
})

// Example to use CustomType, you pause custom time format not in RFC3339
type Demo struct {
    Date  CustomTime `form:"date" query:"date"`
    Title string     `form:"title" query:"title"`
    Body  string     `form:"body" query:"body"`
}

app.Post("/body", func(c fiber.Ctx) error {
    var d Demo
    c.BodyParser(&d)
    fmt.Println("d.Date", d.Date.String())
    return c.JSON(d)
})

app.Get("/query", func(c fiber.Ctx) error {
    var d Demo
    c.QueryParser(&d)
    fmt.Println("d.Date", d.Date.String())
    return c.JSON(d)
})

// curl -X POST -F title=title -F body=body -F date=2021-10-20 http://localhost:3000/body

// curl -X GET "http://localhost:3000/query?title=title&body=body&date=2021-10-20"

Validation

Validation is also possible with the binding methods. You can specify your validation rules using the validate struct tag.

Specify your struct validator in the config

Setup your validator in the config:

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)
}

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

Usage of the validation in the binding methods:

type Person struct {
    Name string `json:"name" validate:"required"`
    Age  int    `json:"age" validate:"gte=18,lte=60"`
}

app.Post("/", func(c fiber.Ctx) error {
    p := new(Person)
    
    if err := c.Bind().JSON(p); err != nil {// <- here you receive the validation errors
        return err
    }
})

Default Fields

You can set default values for fields in the struct by using the default struct tag. Supported types:

  • bool
  • float variants (float32, float64)
  • int variants (int, int8, int16, int32, int64)
  • uint variants (uint, uint8, uint16, uint32, uint64)
  • string
  • a slice of the above types. As shown in the example above, | should be used to separate between slice items.
  • a pointer to one of the above types (pointer to slice and slice of pointers are not supported).
type Person struct {
    Name     string     `query:"name,default:john"`
    Pass     string     `query:"pass"`
    Products []string   `query:"products,default:shoe|hat"`
}

app.Get("/", func(c fiber.Ctx) error {
    p := new(Person)

    if err := c.Bind().Query(p); err != nil {
        return err
    }

    log.Println(p.Name)        // john
    log.Println(p.Pass)        // doe
    log.Println(p.Products)    // ["shoe,hat"]    
    
    // ...
})
// Run tests with the following curl command

// curl "http://localhost:3000/?pass=doe"