English | 简体中文
Fox is a powerful extension of the Gin web framework, offering automatic parameter binding, flexible response rendering, and enhanced features while maintaining full Gin compatibility.
- 🚀 Automatic Binding & Rendering: Bind request parameters and render responses automatically
- 🔧 Handler Flexibility: Support multiple handler signatures with automatic type detection
- 🌐 Multi-Domain Routing: Route traffic based on domain names with exact and regex matching
- ✅ Custom Validation: Implement
IsValiderinterface for complex validation logic - 📊 Structured Logging: Built-in logger with TraceID, structured fields, and file rotation
- ⚡ High Performance: Minimal overhead on top of Gin's already fast routing
- 🔒 Security First: Built-in security scanning and best practices
- 📦 100% Gin Compatible: Use any Gin middleware or feature seamlessly
- Installation
- Quick Start
- Architecture
- Performance
- Examples
- Best Practices
- Troubleshooting
- Security
- Contributing
- License
Fox is currently in beta and under active development. While it offers exciting new features, please note that it may not be stable for production use. If you choose to use, be prepared for potential bugs and breaking changes. Always check the official documentation and release notes for updates and proceed with caution. Happy coding!
Fox requires Go version 1.24 or higher to run. If you need to install or upgrade Go, visit the official Go download page. To start setting up your project. Create a new directory for your project and navigate into it. Then, initialize your project with Go modules by executing the following command in your terminal:
go mod init github.com/your/repoTo learn more about Go modules and how they work, you can check out the Using Go Modules blog post.
After setting up your project, you can install fox with the go get command:
go get -u github.com/fox-gonic/foxThis command fetches the Fox package and adds it to your project's dependencies, allowing you to start building your web applications with Fox.
First you need to import fox package for using fox engine, one simplest example likes the follow example.go:
package main
import (
"github.com/fox-gonic/fox"
)
func main() {
router := fox.New()
router.GET("/ping", func(c *fox.Context) string {
return "pong"
})
router.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}And use the Go command to run the demo:
# run example.go and visit 0.0.0.0:8080/ping on browser
$ go run example.gopackage main
import (
"github.com/fox-gonic/fox"
)
type DescribeArticleArgs struct {
ID int64 `uri:"id"`
}
type CreateArticleArgs struct {
Title string `json:"title"`
Content string `json:"content"`
}
type Article struct {
Title string `json:"title"`
Content string `json:"content"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func main() {
router := fox.New()
router.GET("/articles/:id", func(c *fox.Context, args *DescribeArticleArgs) int64 {
return args.ID
})
router.POST("/articles", func(c *fox.Context, args *CreateArticleArgs) (*Article, error) {
article := &Article{
Title: args.Title,
Content: args.Content,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// Save article to database
return article, nil
})
router.Run()
}package main
import (
"github.com/fox-gonic/fox"
)
var ErrPasswordTooShort = &httperrors.Error{
HTTPCode: http.StatusBadRequest,
Err: errors.New("password too short"),
Code: "PASSWORD_TOO_SHORT",
}
type CreateUserArgs struct {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
}
func (args *CreateUserArgs) IsValid() error {
if args.Username == "" && args.Email == "" {
return httperrors.ErrInvalidArguments
}
if len(args.Password) < 6 {
return ErrPasswordTooShort
}
return nil
}
func main() {
router := fox.New()
router.POST("/users/signup", func(c *fox.Context, args *CreateUserArgs) (*User, error) {
user := &User{
Username: args.Username,
Email: args.Email,
}
// Hash password and save user to database
return user, nil
})
router.Run()
}$ curl -X POST http://localhost:8080/users/signup \
-H 'content-type: application/json' \
-d '{"username": "George", "email": "[email protected]"}'
{"code":"PASSWORD_TOO_SHORT"}Fox extends Gin's routing engine with automatic parameter binding and response rendering:
┌─────────────────────────────────────────────────────────────┐
│ HTTP Request │
└────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Gin Router/Engine │
│ ┌────────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ Middleware 1 │─▶│ Middleware 2 │─▶│ Middleware N │ │
│ └────────────────┘ └──────────────┘ └─────────────────┘ │
└────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Fox Handler Wrapper │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 1. Reflect Handler Signature │ │
│ │ • Detect parameter types (Context, Request, etc) │ │
│ │ • Detect return types (data, error, status) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 2. Automatic Parameter Binding │ │
│ │ • URI parameters (path variables) │ │
│ │ • Query parameters │ │
│ │ • JSON/Form body │ │
│ │ • Custom validation (IsValider) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 3. Execute Handler Function │ │
│ │ • Call with bound parameters │ │
│ │ • Handle panics and errors │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 4. Automatic Response Rendering │ │
│ │ • Detect response type │ │
│ │ • Serialize to JSON │ │
│ │ • Set appropriate HTTP status code │ │
│ │ • Handle httperrors.Error specially │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ HTTP Response │
└─────────────────────────────────────────────────────────────┘
- fox.Engine: Wraps
gin.Enginewith enhanced handler registration - fox.Context: Extends
gin.Contextwith additional methods (RequestBody, TraceID) - call.go: Core reflection-based handler invocation logic
- render.go: Automatic response serialization and rendering
- validator.go: Integration with go-playground/validator and custom IsValider
- DomainEngine: Multi-domain routing with exact and regex pattern matching
Fox adds minimal overhead to Gin's performance while providing significant developer productivity gains:
Tested on Apple M4 Pro, Go 1.25.4:
Routing Benchmarks:
BenchmarkEngine_SimpleRoute 1,700,000 656 ns/op 1554 B/op 20 allocs/op
BenchmarkEngine_ParamRoute 1,700,000 633 ns/op 1554 B/op 20 allocs/op
BenchmarkEngine_MultiParam 1,300,000 879 ns/op 2121 B/op 27 allocs/op
BenchmarkEngine_WildcardRoute 1,900,000 611 ns/op 1579 B/op 20 allocs/op
BenchmarkEngine_JSONResponse 1,600,000 732 ns/op 1767 B/op 21 allocs/op
Binding Benchmarks:
BenchmarkBinding_URIParam 900,000 1283 ns/op 2717 B/op 36 allocs/op
BenchmarkBinding_QueryParam 600,000 1653 ns/op 3010 B/op 40 allocs/op
BenchmarkBinding_JSONBody 500,000 1878 ns/op 3566 B/op 42 allocs/op
BenchmarkBinding_WithValidation 500,000 2094 ns/op 3702 B/op 43 allocs/op
BenchmarkBinding_NoBinding (baseline) 1,700,000 643 ns/op 1597 B/op 22 allocs/op
Middleware Benchmarks:
BenchmarkEngine_WithMiddleware 800,000 1163 ns/op 2675 B/op 35 allocs/op
BenchmarkEngine_MultipleMiddlewares 500,000 2304 ns/op 4922 B/op 65 allocs/op
| Feature | Time (ns/op) | Overhead vs Baseline | Notes |
|---|---|---|---|
| Simple string return | ~656 | Baseline | Direct response rendering |
| Parameter binding (URI) | ~1283 | +95% | Reflection + struct allocation |
| Parameter binding (JSON) | ~1878 | +186% | JSON parsing + validation |
| JSON response | ~732 | +12% | JSON serialization |
| Single middleware | ~1163 | +77% | Middleware chain execution |
| Complex nested struct | ~2812 | +328% | Deep JSON parsing + validation |
Key Insight: The overhead is primarily from JSON parsing/serialization, not Fox's reflection logic. For most real-world applications, this is negligible compared to database queries and business logic.
To run the benchmarks yourself:
# Run all benchmarks
go test -bench=. -benchmem
# Run specific benchmark
go test -bench=BenchmarkEngine_SimpleRoute -benchmem
# Run with more iterations for accurate results
go test -bench=. -benchmem -benchtime=10s
# Save results to file
go test -bench=. -benchmem > benchmark_results.txtUse Fox when:
- Building REST APIs with many endpoints
- Need automatic parameter validation
- Want cleaner, more maintainable handler signatures
- Working with JSON request/response bodies
Use Gin directly when:
- Every microsecond matters (high-frequency trading, etc.)
- Need maximum control over request/response handling
- Building static file servers or proxies
Comprehensive examples are available in the examples/ directory:
| Example | Description |
|---|---|
| 01-basic | Basic routing, path parameters, JSON responses |
| 02-binding | Parameter binding (JSON/URI/Query) with validation |
| 03-middleware | Custom middleware, authentication, rate limiting |
| 04-domain-routing | Multi-domain and multi-tenant routing |
| 05-custom-validator | Complex validation with IsValider interface |
| 06-error-handling | HTTP errors, custom error codes |
| 07-logger-config | Logger configuration, file rotation, JSON logs |
Each example includes a README with usage instructions and curl commands.
Use httperrors.Error for API errors:
import "github.com/fox-gonic/fox/httperrors"
var ErrUserNotFound = &httperrors.Error{
HTTPCode: http.StatusNotFound,
Code: "USER_NOT_FOUND",
Err: errors.New("user not found"),
}
router.GET("/users/:id", func(ctx *fox.Context) (*User, error) {
user, err := findUser(ctx.Param("id"))
if err != nil {
return nil, ErrUserNotFound
}
return user, nil
})Combine struct tags with IsValider:
type CreateUserRequest struct {
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=8"`
Age int `json:"age" binding:"gte=18,lte=150"`
}
func (r *CreateUserRequest) IsValid() error {
if strings.Contains(r.Email, "disposable.com") {
return &httperrors.Error{
HTTPCode: http.StatusBadRequest,
Code: "INVALID_EMAIL_DOMAIN",
Err: errors.New("disposable email addresses not allowed"),
}
}
return nil
}Use logger with fields for better observability:
import "github.com/fox-gonic/fox/logger"
router.POST("/orders", func(ctx *fox.Context, req *CreateOrderRequest) (*Order, error) {
log := logger.NewWithContext(ctx.Context)
log.WithFields(map[string]interface{}{
"user_id": req.UserID,
"amount": req.Amount,
}).Info("Creating order")
order, err := createOrder(req)
if err != nil {
log.WithError(err).Error("Order creation failed")
return nil, err
}
return order, nil
})Choose the right signature for your use case:
// Simple: No binding needed
router.GET("/health", func(ctx *fox.Context) string {
return "OK"
})
// With binding: Automatic parameter extraction
router.GET("/users/:id", func(ctx *fox.Context, req *GetUserRequest) (*User, error) {
return findUser(req.ID)
})
// Full control: Access context and return custom status
router.POST("/complex", func(ctx *fox.Context, req *Request) (interface{}, int, error) {
result, err := process(req)
if err != nil {
return nil, http.StatusInternalServerError, err
}
return result, http.StatusCreated, nil
})Configure logging for production:
import "github.com/fox-gonic/fox/logger"
logger.SetConfig(&logger.Config{
LogLevel: logger.InfoLevel,
ConsoleLoggingEnabled: true,
FileLoggingEnabled: true,
Filename: "/var/log/myapp/app.log",
MaxSize: 100, // MB
MaxBackups: 30,
MaxAge: 90, // days
EncodeLogsAsJSON: true,
})
router := fox.New()
router.Use(fox.Logger(fox.LoggerConfig{
SkipPaths: []string{"/health", "/metrics"},
}))Organize routes by domain:
de := fox.NewDomainEngine()
// API subdomain
de.Domain("api.example.com", func(apiRouter *fox.Engine) {
apiRouter.GET("/v1/users", listUsers)
apiRouter.POST("/v1/users", createUser)
})
// Admin subdomain
de.Domain("admin.example.com", func(adminRouter *fox.Engine) {
adminRouter.Use(AuthMiddleware())
adminRouter.GET("/dashboard", showDashboard)
})
// Wildcard for tenant subdomains
de.DomainRegexp(`^(?P<tenant>[a-z0-9-]+)\.example\.com$`, func(tenantRouter *fox.Engine) {
tenantRouter.GET("/", func(ctx *fox.Context) string {
tenant := ctx.Param("tenant")
return "Welcome, " + tenant
})
})
http.ListenAndServe(":8080", de)Problem: Request validation fails with unclear error messages.
Solution: Check struct tags and use binding tag correctly:
// Incorrect
type Request struct {
Email string `json:"email" validate:"email"` // Wrong tag
}
// Correct
type Request struct {
Email string `json:"email" binding:"required,email"`
}Problem: Routes return 404 even though they're registered.
Solution:
- Ensure path parameters match:
/users/:idvs/users/:user_id - Check HTTP method:
GETvsPOST - Verify domain routing configuration if using DomainEngine
- Enable debug mode to see registered routes:
fox.SetMode(fox.DebugMode)Problem: invalid character or cannot unmarshal errors.
Solution:
- Verify Content-Type header is
application/json - Check JSON structure matches struct tags
- Use proper field types (string vs int)
# Correct
curl -H "Content-Type: application/json" -d '{"name":"Alice"}' http://localhost:8080/users
# Missing header (may fail)
curl -d '{"name":"Alice"}' http://localhost:8080/usersProblem: IsValid() method not being invoked.
Solution: Ensure pointer receivers and correct interface:
// Correct
func (r *CreateUserRequest) IsValid() error {
return nil
}
// Incorrect (value receiver won't work)
func (r CreateUserRequest) IsValid() error {
return nil
}Problem: Application panics when registering domain with invalid regex.
Solution: Validate regex patterns before registration:
pattern := `^(?P<tenant>[a-z0-9-]+)\.example\.com$`
if _, err := regexp.Compile(pattern); err != nil {
log.Fatal("Invalid regex:", err)
}
de.DomainRegexp(pattern, handler)Problem: Memory usage increases over time.
Possible causes:
- Logger file handles not being closed (check MaxBackups/MaxAge)
- Large response bodies not being garbage collected
- Middleware memory leaks
Solution:
// Configure log rotation properly
logger.SetConfig(&logger.Config{
MaxBackups: 10, // Keep only 10 old files
MaxAge: 30, // Delete files older than 30 days
})
// Use context deadlines for long-running requests
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
defer cancel()Enable debug mode to see detailed information:
fox.SetMode(fox.DebugMode) // Development
fox.SetMode(fox.ReleaseMode) // ProductionIn debug mode, Fox will print:
- Registered routes and their handlers
- Request binding details
- Middleware execution order
- Check the examples/ directory
- Review CONTRIBUTING.md for guidelines
- Search existing GitHub Issues
- Open a new issue with:
- Fox and Go versions
- Minimal reproducible example
- Expected vs actual behavior
Fox takes security seriously. We implement multiple layers of security scanning:
- govulncheck: Scans for known vulnerabilities in Go dependencies
- CodeQL: Static Application Security Testing (SAST) for code analysis
- Dependency Review: Reviews dependency changes in pull requests
- Weekly Scans: Automated security scans run every Monday
# Install govulncheck
go install golang.org/x/vuln/cmd/govulncheck@latest
# Run vulnerability scan
govulncheck ./...- SECURITY.md - Security policy and vulnerability reporting
- SECURITY_SCAN.md - Detailed security scanning documentation
If you discover a security vulnerability, please refer to SECURITY.md for our responsible disclosure process. Do not open public GitHub issues for security vulnerabilities.
We welcome contributions! Please see CONTRIBUTING.md for details on how to contribute to Fox.
Fox is released under the MIT License. See LICENSE for details.