Go-Reqbind is a Go library that automatically unmarshals HTTP requests into structured Go types and validates them using go-playground/validator. It supports extracting data from headers, query parameters, path parameters, and request bodies -- all driven by struct tags.
- Features
- Installation
- Quick Start
- Usage
- Validation
- API Reference
- Field Name Resolution
- Supported Types
- Error Handling
- Examples
- Contributing
- License
- Acknowledgments
- 🔄 Automatic Unmarshaling: Extract data from headers, query parameters, path parameters, and request bodies into Go structs
- âś… Built-in Validation: Integrated with go-playground/validator for robust validation
- 🎯 Custom Validations: Support for custom validation functions
- 📝 Nested Structures: Handle complex nested query parameters and JSON bodies
- 🔍 Detailed Error Messages: Get clear, structured validation error messages
- 🏷️ Flexible Tagging: Use struct tags to map request data to struct fields
go get go.companyinfo.dev/go-reqbindpackage main
import (
"net/http"
"go.companyinfo.dev/go-reqbind"
)
type CreateUserRequest struct {
Body struct {
Name string `json:"name" validate:"required,min=3"`
Email string `json:"email" validate:"required,email"`
} `goReq:"body"`
}
func handler(w http.ResponseWriter, r *http.Request) {
req := &CreateUserRequest{}
validationErrors, err := reqbind.UnmarshalAndValidate(*r, req, nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if validationErrors != nil {
// Handle validation errors
return
}
// Use req.Body.Name and req.Body.Email
}Go-Reqbind uses struct tags to identify which parts of the HTTP request should be unmarshaled:
goReq:"header"- Extract from HTTP headersgoReq:"query"- Extract from query parametersgoReq:"path"- Extract from path parameters (requires URL template)goReq:"body"- Extract from request body (JSON)
Go-Reqbind supports nested query parameters using bracket notation (e.g., filter[user][name]=value).
type SearchRequest struct {
QueryParams struct {
Name string
Page int
Limit int
} `goReq:"query"`
}
// URL: /search?name=john&page=1&limit=10type FilterRequest struct {
QueryParams struct {
Filter struct {
User struct {
Name string
Email string
}
}
Page struct {
Size int
Number int
}
} `goReq:"query"`
}
// URL: /users?filter[user][name]=John&filter[user][email]=john@example.com&page[size]=20&page[number]=1You can use the goReq tag to map query parameters to different struct field names:
type MyRequest struct {
QueryParams struct {
Name string `goReq:"name"`
Filter struct {
User struct {
Id string `goReq:"userID"`
Profile struct {
Age struct {
Min uint8 `goReq:"min"`
Max uint8 `goReq:"max"`
} `goReq:"ageRange"`
} `goReq:"profile"`
}
}
} `goReq:"query"`
}
// URL: /users?name=john&filter[user][userID]=123&filter[user][profile][age_range][min]=18&filter[user][profile][age_range][max]=65type SearchRequest struct {
QueryParams struct {
Filter struct {
User struct {
Interests []string
Tags []string
}
}
} `goReq:"query"`
}
// URL: /search?filter[user][interests]=coding,reading,traveling&filter[user][tags]=developer,gotype FilterRequest struct {
QueryParams struct {
Filter struct {
Year struct {
Min *int
Max *int
}
}
} `goReq:"query"`
}
// URL: /search?filter[year][min]=2021
// Min will be set to 2021, Max will be niltype SearchRequest struct {
QueryParams struct {
Filter struct {
Metadata map[string]any
}
} `goReq:"query"`
}
// URL: /search?filter[metadata][reportType]=general-report&filter[metadata][domain]=nlExtract HTTP headers into struct fields. Header matching is case-insensitive.
type MyRequest struct {
Header struct {
UserID string `goReq:"X-User-Id" validate:"required"`
ContentType string `goReq:"Content-Type"`
AcceptEncoding []string `goReq:"Accept-Encoding"`
} `goReq:"header"`
}
// Headers:
// X-User-Id: user-123
// Content-Type: application/json
// Accept-Encoding: gzip, deflate, brNote:
- Header names are matched case-insensitively
- Multiple values for the same header are joined with commas for strings, or stored as a slice for slice fields
- Use
goReq:"-"to skip a field during unmarshaling but still validate it
Path parameters require a URL template to extract values from the URL path.
type GetUserProfileRequest struct {
PathParams struct {
UserID string `goReq:"userID" validate:"uuid"`
Year string `goReq:"year"`
} `goReq:"path"`
}
// URL: /api/users/6f3c1b3e-9d42-4f7a-8b8a-3b8d2e2e7c91/profile/2024
// Template: /api/users/:userID/profile/:year
options := &reqbind.Options{
URLTemplate: "/api/users/:userID/profile/:year",
}
validationErrors, err := reqbind.UnmarshalAndValidate(*r, req, options)Extract JSON request body into struct fields. Uses standard json tags.
type CreateUserProfileRequest struct {
Body struct {
FirstName string `json:"firstName" validate:"required,min=2"`
LastName string `json:"lastName" validate:"required,min=2"`
Email string `json:"email" validate:"required,email"`
Phone string `json:"phone,omitempty"`
Address struct {
Street string `json:"street,omitempty"`
City string `json:"city,omitempty"`
Postcode string `json:"postcode,omitempty"`
Country string `json:"country,omitempty"`
} `json:"address,omitempty"`
} `goReq:"body"`
}
// Request Body:
// {
// "firstName": "John",
// "lastName": "Doe",
// "email": "john@example.com",
// "phone": "+1234567890",
// "address": {
// "street": "123 Main St",
// "city": "New York",
// "postcode": "10001",
// "country": "USA"
// }
// }type GetUserRequest struct {
Header struct {
UserID string `goReq:"X-User-Id" validate:"required"`
ContentType string `goReq:"Content-Type"`
AcceptEncoding []string `goReq:"Accept-Encoding"`
} `goReq:"header"`
PathParams struct {
UserID string `goReq:"userID" validate:"uuid"`
} `goReq:"path"`
QueryParams struct {
Filter struct {
User struct {
Name string `validate:"required"`
Profile struct {
FirstName string `validate:"required,min=2,max=50"`
LastName string `validate:"required,min=2,max=50"`
Email string `validate:"required,email"`
Age uint8 `validate:"required,numeric,gte=18,lte=120"`
Gender string `validate:"omitempty,oneof=male female prefer_not_to"`
}
Address struct {
Postcode string `validate:"omitempty,len=5"`
}
}
}
Page struct {
Size int
Number int
}
} `goReq:"query"`
}
func handler(w http.ResponseWriter, r *http.Request) {
req := &GetUserRequest{}
options := &reqbind.Options{
URLTemplate: "/api/users/:userID",
}
validationErrors, err := reqbind.UnmarshalAndValidate(*r, req, options)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if validationErrors != nil {
// Return validation errors
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(validationErrors)
return
}
// Process the request
// Access: req.Header.UserID, req.PathParams.UserID, req.QueryParams.Filter.User.Name, etc.
}Go-Reqbind integrates with go-playground/validator for validation. Use the validate tag on struct fields.
required- Field must be present and not emptyemail- Must be a valid email addressmin=3- Minimum length/valuemax=100- Maximum length/valuenumeric- Must be numericuuid- Must be a valid UUIDoneof=value1 value2- Must be one of the specified valuesomitempty- Validate only if field is not emptydive- Validate nested structs/slices
Validation errors are returned as a slice of ValidationError structs:
type ValidationError struct {
Detail string `json:"detail"`
Source struct {
Pointer string `json:"pointer,omitempty"` // For body errors (JSON pointer)
Parameter string `json:"parameter,omitempty"` // For query/path errors
Header string `json:"header,omitempty"` // For header errors
} `json:"source,omitempty"`
}validationErrors, err := reqbind.UnmarshalAndValidate(*r, req, options)
if err != nil {
// Handle unmarshaling error
return
}
if validationErrors != nil {
for _, ve := range validationErrors {
fmt.Printf("Error in %s: %s\n", ve.Source.Parameter, ve.Detail)
}
return
}You can register custom validation functions:
// Define custom validation function
func isEven(fl validator.FieldLevel) bool {
if num, ok := fl.Field().Interface().(int); ok {
return num%2 == 0
}
return false
}
// Use in request struct
type MyRequest struct {
Body struct {
Age int `json:"age" validate:"is-even"`
} `goReq:"body"`
}
// Register custom validation
options := &reqbind.Options{
CustomValidations: map[string]validator.Func{
"is-even": isEven,
},
}
validationErrors, err := reqbind.UnmarshalAndValidate(*r, req, options)type MyRequest struct {
Body struct {
Users []struct {
Profiles []struct {
Name string `json:"name" validate:"required,min=4"`
} `json:"profiles" validate:"required,dive"`
} `json:"users" validate:"required,dive"`
} `goReq:"body"`
}
// Request Body:
// {
// "users": [
// {
// "profiles": [
// {"name": "John Doe"},
// {"name": "Bob"} // This will fail validation (min=4)
// ]
// }
// ]
// }Unmarshals an HTTP request into the target struct without validation.
Parameters:
request: The HTTP request to unmarshaltarget: Pointer to a struct that will be populatedurlTemplate: URL template for path parameter extraction (e.g.,/users/:id)
Returns:
error: Error if unmarshaling fails
Unmarshals an HTTP request into the target struct and validates it.
Parameters:
request: The HTTP request to unmarshaltarget: Pointer to a struct that will be populatedoptions: Optional configuration (can benil)
Returns:
[]ValidationError: Slice of validation errors (nil if validation passes)error: Error if unmarshaling fails
type Options struct {
URLTemplate string // URL template for path parameters
CustomValidations map[string]validator.Func // Custom validation functions
}Go-Reqbind uses the following priority to resolve field names:
- Struct tag (
goReqtag) - If present, uses the tag value - Field name - Converts the query parameter name to TitleCase and matches against struct field names
For example:
- Query param
namematches fieldNameor field with taggoReq:"name" - Query param
filter[user][email]matches nested structFilter.User.Emailor field with taggoReq:"email"
stringint,int8,int16,int32,int64uint,uint8,uint16,uint32,uint64float32,float64[]string(comma-separated values)*int(pointers for optional values)map[string]any
stringint[]string
stringint
- Any type supported by
encoding/json
If unmarshaling fails (e.g., invalid JSON, malformed query string), Unmarshal and UnmarshalAndValidate return an error.
If validation fails, UnmarshalAndValidate returns a slice of ValidationError structs with detailed information about each validation failure.
For complete, runnable examples covering all features, see the examples/ directory.
Contributions are welcome! Here's how you can contribute:
- Fork the repository
- Create a new branch (
git checkout -b feature/improvement) - Make your changes
- Commit your changes (
git commit -am 'Add new feature') - Push to the branch (
git push origin feature/improvement) - Create a Pull Request
Please make sure to:
- Follow the existing code style
- Add tests if applicable
- Update documentation as needed
- Include a clear description of your changes in the PR
This project is licensed under the Apache License 2.0.
- Built on top of go-playground/validator
- Uses golang.org/x/text for text processing