Basic Usage
5 minute read
This guide covers the essential operations for working with the binding package: binding from different sources, understanding the API variants, and handling errors.
Generic API vs Non-Generic API
The binding package provides two API styles:
Generic API (Recommended)
Use the generic API when you know the type at compile time:
// Type is specified as a type parameter
user, err := binding.JSON[CreateUserRequest](body)
params, err := binding.Query[ListParams](r.URL.Query())
Benefits:
- Compile-time type safety
- Cleaner syntax
- Better IDE support
- No need to pre-allocate the struct
Non-Generic API
Use the non-generic API when the type comes from a variable or when working with interfaces:
var user CreateUserRequest
err := binding.JSONTo(body, &user)
var params ListParams
err := binding.QueryTo(r.URL.Query(), ¶ms)
Use when:
- Type is determined at runtime
- Working with reflection
- Integrating with older codebases
Binding from Different Sources
JSON Body
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
// Read body from request
body, err := io.ReadAll(r.Body)
if err != nil {
// Handle error
}
defer r.Body.Close()
// Bind JSON to struct
user, err := binding.JSON[CreateUserRequest](body)
if err != nil {
// Handle binding error
}
Query Parameters
type ListParams struct {
Page int `query:"page" default:"1"`
Limit int `query:"limit" default:"20"`
Search string `query:"search"`
Tags []string `query:"tags"`
}
params, err := binding.Query[ListParams](r.URL.Query())
Path Parameters
type UserIDParam struct {
UserID int `path:"user_id"`
}
// Path params typically come from your router
// Example with common router pattern:
pathParams := map[string]string{
"user_id": "123",
}
params, err := binding.Path[UserIDParam](pathParams)
Form Data
type LoginForm struct {
Username string `form:"username"`
Password string `form:"password"`
Remember bool `form:"remember"`
}
// Parse form first
if err := r.ParseForm(); err != nil {
// Handle parse error
}
form, err := binding.Form[LoginForm](r.Form)
Headers
type RequestHeaders struct {
Auth string `header:"Authorization"`
ContentType string `header:"Content-Type"`
UserAgent string `header:"User-Agent"`
}
headers, err := binding.Header[RequestHeaders](r.Header)
Cookies
type SessionCookies struct {
SessionID string `cookie:"session_id"`
CSRF string `cookie:"csrf_token"`
}
cookies, err := binding.Cookie[SessionCookies](r.Cookies())
Error Handling Basics
All binding functions return an error that provides context about what went wrong:
user, err := binding.JSON[CreateUserRequest](body)
if err != nil {
// Check for specific error types
var bindErr *binding.BindError
if errors.As(err, &bindErr) {
fmt.Printf("Field %s: %v\n", bindErr.Field, bindErr.Err)
}
// Or just use the error message
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
Common error types:
BindError- Field-level binding error with contextUnknownFieldError- Unknown fields in strict modeMultiError- Multiple errors when usingWithAllErrors()
See Error Handling for detailed information.
Default Values
Use the default tag to specify fallback values:
type Config struct {
Port int `query:"port" default:"8080"`
Host string `query:"host" default:"localhost"`
Debug bool `query:"debug" default:"false"`
Timeout string `query:"timeout" default:"30s"`
}
// If query params don't include these values, defaults are used
cfg, err := binding.Query[Config](r.URL.Query())
Working with Pointers
Use pointers to distinguish between “not set” and “set to zero value”:
type UpdateUserRequest struct {
Name *string `json:"name"` // nil = not updating, "" = clear value
Email *string `json:"email"`
Age *int `json:"age"` // nil = not updating, 0 = set to zero
}
user, err := binding.JSON[UpdateUserRequest](body)
// Check if field was provided
if user.Name != nil {
// Update name to *user.Name
}
if user.Age != nil {
// Update age to *user.Age
}
Common Patterns
API Handler Pattern
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
// Read body
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read body", http.StatusBadRequest)
return
}
defer r.Body.Close()
// Bind request
req, err := binding.JSON[CreateUserRequest](body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Process request
user := createUser(req)
// Send response
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
Query + Path Parameters
type GetUserRequest struct {
UserID int `path:"user_id"`
Format string `query:"format" default:"json"`
}
func GetUserHandler(w http.ResponseWriter, r *http.Request) {
req, err := binding.Bind[GetUserRequest](
binding.FromPath(pathParams), // From router
binding.FromQuery(r.URL.Query()),
)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
user := getUserByID(req.UserID)
// Format response according to req.Format
}
Form with CSRF Token
type EditForm struct {
Title string `form:"title"`
Content string `form:"content"`
CSRF string `form:"csrf_token"`
}
func EditHandler(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, "Invalid form", http.StatusBadRequest)
return
}
form, err := binding.Form[EditForm](r.Form)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Verify CSRF token
if !verifyCRSF(form.CSRF) {
http.Error(w, "Invalid CSRF token", http.StatusForbidden)
return
}
// Process form
}
Type Conversion
The binding package automatically converts string values to appropriate types:
type Request struct {
// String to int
Page int `query:"page"` // "123" -> 123
// String to bool
Active bool `query:"active"` // "true" -> true
// String to float
Price float64 `query:"price"` // "19.99" -> 19.99
// String to time.Duration
Timeout time.Duration `query:"timeout"` // "30s" -> 30 * time.Second
// String to time.Time
CreatedAt time.Time `query:"created"` // "2025-01-01" -> time.Time
// String to slice
Tags []string `query:"tags"` // "go,rust,python" -> []string
}
See Type Support for complete type conversion details.
Performance Tips
- Reuse request bodies: Binding consumes the body, so read it once and reuse
- Use defaults: Struct tags with defaults avoid unnecessary error checking
- Cache reflection: Happens automatically, but avoid dynamic struct generation
- Stream large payloads: Use
JSONReaderfor bodies > 1MB
Next Steps
- Learn about Query Parameters in detail
- Explore JSON Binding for request bodies
- See Multi-Source for combining data
- Master Struct Tags syntax and options
For complete API documentation, see API Reference.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.