Response Structure

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() (the error value returned by our handler)
  • Wraps everything in our standard response structure and sends it as JSON

Complete Example

main.go

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:

img_3.png

And with missing parameters at http://127.0.0.1:8000/:

img_5.png

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.