Add a controller

Before we adding any logic to our application, first we need a controller and its route. Remember the hello controller? You have used it in the previous chapter.

        • hello.go
      • hello.go
        • hello_new.go
        • hello_v1_hello.go
        • hello.go
  • The route is registered in internal/cmd/cmd.go:

    s.Group("/", func(group *ghttp.RouterGroup) {
        group.Middleware(ghttp.MiddlewareHandlerResponse)
    	group.Bind(
    	    hello.NewV1(),
    	)
    })

    This method using here is called Standardized Routing in GoFrame, which is easy to use and could be combined with code generation command gf gen ctrl. The API documentation would also be generated automatically, see Swagger page hello.

    We recommend to use Standardized Routing since it would be better to maintain and extend. You could also find other ways of routing here.

    Create API structure

    To use the code generation command gf gen ctrl, you need to declare the API structure first. GoFrame require certain file structure to generate controller, which is api/{model}/{version}/{definition}.go. And the API structure name is also require to end with Req and Res. Let’s create a new API in RESTful way for our message:

    api/message/v1/store.go
    package v1
    
    import "github.com/gogf/gf/v2/frame/g"
    
    type CreateMessageReq struct {
    	g.Meta  `path:"/" tags:"Message" method:"post" sum:"Create a message"`
    	UserUid string `json:"user_uid" v:"required|size:10" des:"Message sender ID" eg:"0000000000"`
    	Content string `json:"content" v:"required|length:1,100" des:"Message content" eg:"This is my first message."`
    }
    
    type CreateMessageRes struct {
    	g.Meta  `status:"201" mime:"application/json"`
    	Code    int         `json:"code" v:"required" des:"Status code" eg:"0"`
    	Message string      `json:"message" v:"required" des:"Status message" eg:"Success"`
    	Data    interface{} `json:"data"`
    }
    
    type CreateMessageRes500 struct{
    	g.Meta  `resEg:"your_path/example.json"`
    }
    
    func (r CreateMessageRes) ResponseStatusMap() map[goai.StatusCode]any {
    	return map[goai.StatusCode]any{
    		500: CreateMessageRes500{},
    	}
    }

    The g.Meta here is used to add additional attributes to Request or Response. For this request, path is used to define the route, tags is used to set group in OpenAPI documentation, method is used to declare the accept HTTP method and sum is used to add summary to the route. And for the response, status is used to set the default response status code, mime is just used to set the response content type.

    Here we also define the 500 response structure, where the resEg tag (abbreviation for responseExample) is used to set the path of the response example file. Currently, the available example json file formats can be the following two types:

    example.json
    [
      {
        "code": 0,
        "message": "Success",
    	"data": null
      },
      {
        "code": 1,
        "message": "Internal Server Error",
    	"data": null
      }
    ]
    example.json
    {
    	"success": {
    		"code": 0,
    		"message": "Success",
    		"data": null
    	},
    	"error": {
    		"code": 1,
    		"message": "Internal Server Error",
    		"data": null
    	}
    }

    Array type will automatically add example name according to the order, while Object type can be manually specified.

    As long as the response structure implements the goai.IEnhanceResponseStatus interface (adds the EnhanceResponseStatus method), it can automatically add additional response status codes and response structures during the document generation process. If the response structure contains any fields or sets the mime tag, it will automatically override the default content in OpenAPI documentation (usually it is some common response structures added automatically).

    Tags in structure could not only be used to generate OpenAPI documentation, but also to validate data or transform data. For example, v tag could be used to validate data in request. For our request here, the UserUID is set to be required and the length should be 10, similar for other fields. Documentation for validation here.

    Those contents seem a little confusing, but don’t worry, you will see their function clearly soon after we register the route. Or you could also find more details for gtag here.

    ℹ️
    status resEg responseExample tags and the ResponseStatusMap method of response structure are available since v2.8.0.

    Generate Controller

    Now we could use gf gen ctrl to generate controller with those structures declared before:

    & gf gen ctrl
    generated: ...\api\hello\hello.go
    generated: ...\api\message\message.go
    generated: internal\controller\message\message.go
    generated: internal\controller\message\message_new.go
    generated: internal\controller\message\message_v1_create_message.go
    done!

    And you will see its structure is similar to the hello controller auto-generated before.

    ℹ️
    To avoid running this command every time after changing some code in api directory, you could use some auto-run plugin, like Run on Save in VSCode.

    Open the internal\controller\message\message_v1_create_message.go, you will see the implementation of controller here:

    func (c *ControllerV1) CreateMessage(ctx context.Context, req *v1.CreateMessageReq) (res *v1.CreateMessageRes, err error) {
    	return nil, gerror.NewCode(gcode.CodeNotImplemented)
    }

    The response is not implemented yet, we may add some code later.

    Register route

    After generate controller, we need to register the route. Open the cmd.go in internal\cmd folder, the hello we used before has been registered here:

    internal/cmd/cmd.go
    s.Group("/", func(group *ghttp.RouterGroup) {
    	group.Middleware(ghttp.MiddlewareHandlerResponse)
    	group.Bind(
    		hello.NewV1(),
    	)
    })

    We could add the message similarly:

    internal/cmd/cmd.go
    s.Group("/messages", func(group *ghttp.RouterGroup) {
    	group.Middleware(ghttp.MiddlewareHandlerResponse)
    	group.Bind(
    		message.NewV1(),
    	)
    })

    Since the path we set before is /, we need to add messages to the route group when registering. You could also set the path in api structure to /messages, and you can ignore the messages here.

    route used for controller if api path is set to /messages
    internal/cmd/cmd.go
    s.Group("/", func(group *ghttp.RouterGroup) {
    	group.Middleware(ghttp.MiddlewareHandlerResponse)
    	group.Bind(
    		hello.NewV1(),
    		message.NewV1(),
    	)
    })

    Test your result

    Good job! You have successfully created your first API and registered the route. Check it in Swagger here.

    If you have used tools like Postman, you could test the API with the following request:

    POST http://localhost:8000/messages

    {
      "user_uid": "0000000000",
      "content": "This is my first message."
    }

    Or use curl to test the API:

    curl -X POST -H "Content-Type: application/json" -d '{"user_uid":"0000000000","content":"This is my first message."}' "http://localhost:8000/messages"

    You will see the Not Implemented response like this:

    {
      "code": 58,
      "message": "Not Implemented",
      "data": null
    }