The GoFrame
framework provides a standardized routing approach that simplifies route registration and lets developers focus on business logic. We call this Standard Routing - a more intuitive and maintainable way to handle HTTP endpoints.
Defining Request/Response Types
Standard routing uses structured types for both requests and responses. This approach enables parameter validation, documentation generation, and future extensibility:
type HelloReq struct {
g.Meta `path:"/" method:"get"`
Name string `v:"required" dc:"User's name"`
Age int `v:"required" dc:"User's age"`
}
type HelloRes struct {}
Key components:
- The request struct embeds
g.Meta
with routing metadata tags:path
: The URL path to registermethod
: The HTTP method to handle (GET, POST, etc.)
- Field tags provide additional functionality:
v
: Validation rules (short for “valid”). Here,v:"required"
marks fields as mandatorydc
: Documentation comments (short for “description”) for API docs
info
For a complete reference of available tags and validation rules, check out the relevant chapters in our development manual. For now, just focus on understanding their basic purpose.
Object-Oriented Route Handlers
For better maintainability, especially in larger APIs, we use an object-oriented approach instead of registering routes one by one. Here’s how it works:
type Hello struct{}
func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) {
r := g.RequestFromCtx(ctx)
r.Response.Writef(
"Hello %s! Your Age is %d",
req.Name,
req.Age,
)
return
}
Benefits of this approach:
- Routes are organized into logical groups via handler objects
- Method signatures are more idiomatic and business-focused compared to raw
func(*ghttp.Request)
handlers - The original request object is still accessible via
g.RequestFromCtx
when needed
Complete Example
Here’s how we’d rewrite our web server using standard routing:
main.go
package main
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
type HelloReq struct {
g.Meta `path:"/" method:"get"`
Name string `v:"required" dc:"User's name"`
Age int `v:"required" dc:"User's age"`
}
type HelloRes struct{}
type Hello struct{}
func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) {
r := g.RequestFromCtx(ctx)
r.Response.Writef(
"Hello %s! Your Age is %d",
req.Name,
req.Age,
)
return
}
func main() {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.Bind(
new(Hello),
)
})
s.SetPort(8000)
s.Run()
}
Key points:
s.Group
creates a route group with a common prefix (“/
“ in this case)group.Bind
registers all public methods of our handler object automatically, using struct metadata to configure routes
Testing the API
Let’s test with valid parameters at http://127.0.0.1:8000/?name=john&age=18:
When we try an invalid request at http://127.0.0.1:8000/, we get no output. This is because the validation failed before reaching our handler - the server rejected the request due to missing required parameters.
What’s Next?
While we’ve improved our routing structure, we still need better control over error handling. For example, how can we:
- Capture and customize validation error messages?
- Format error responses consistently?
We’ll tackle these questions in the next chapter.