In this chapter, we'll implement a standardized response format for our API endpoints. All responses, whether successful or not, will be returned in a consistent JSON format.
Response Structure
Let's define our standard response structure:
type Response struct {
Message string `json:"message" dc:"Response message"`
Data interface{} `json:"data" dc:"Response payload"`
}
We've added json
tags to specify how each field should be serialized in the JSON response.
API Contracts
type HelloReq struct {
g.Meta `path:"/" method:"get"`
Name string `v:"required" json:"name" dc:"User's name"`
Age int `v:"required" json:"age" dc:"User's age"`
}
type HelloRes struct {
Content string `json:"content" dc:"Response content"`
}
- The
HelloRes
struct defines the shape of our successful response data - All fields include
json
tags for proper serialization
Handler Implementation
type Hello struct{}
func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) {
res = &HelloRes{
Content: fmt.Sprintf(
"Hello %s! Your Age is %d",
req.Name,
req.Age,
),
}
return
}
Instead of directly writing to the response as in previous examples, we now return a structured response using HelloRes
.
Response Middleware
func Middleware(r *ghttp.Request) {
r.Middleware.Next()
var (
msg string
res = r.GetHandlerResponse()
err = r.GetError()
)
if err != nil {
msg = err.Error()
} else {
msg = "OK"
}
r.Response.WriteJson(Response{
Message: msg,
Data: res,
})
}
This middleware:
- Gets the handler's response using
r.GetHandlerResponse()
(this is the*HelloRes
returned by our handler) - Checks for errors using
r.GetError()
(theerror
value returned by our handler) - Wraps everything in our standard response structure and sends it as JSON
Complete Example
package main
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
type Response struct {
Message string `json:"message" dc:"Response message"`
Data interface{} `json:"data" dc:"Response payload"`
}
type HelloReq struct {
g.Meta `path:"/" method:"get"`
Name string `v:"required" json:"name" dc:"User's name"`
Age int `v:"required" json:"age" dc:"User's age"`
}
type HelloRes struct {
Content string `json:"content" dc:"Response content"`
}
type Hello struct{}
func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) {
res = &HelloRes{
Content: fmt.Sprintf(
"Hello %s! Your Age is %d",
req.Name,
req.Age,
),
}
return
}
func Middleware(r *ghttp.Request) {
r.Middleware.Next()
var (
msg string
res = r.GetHandlerResponse()
err = r.GetError()
)
if err != nil {
msg = err.Error()
} else {
msg = "OK"
}
r.Response.WriteJson(Response{
Message: msg,
Data: res,
})
}
func main() {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(Middleware)
group.Bind(
new(Hello),
)
})
s.SetPort(8000)
s.Run()
}
Testing the API
Let's test with valid parameters at http://127.0.0.1:8000/?name=john&age=18:
And with missing parameters at http://127.0.0.1:8000/:
Looking Ahead
We've successfully implemented a standardized JSON response format, which is crucial for maintaining consistency across large APIs.
Notice how we've structured everything - from input parameters to response formats - with clear types, descriptions, and validation rules. This structured approach opens up interesting possibilities: could we automatically generate API documentation from these definitions? Indeed we can, and that's exactly what we'll explore in the next chapter.