--The generator that automatically generates from the Go structure described in the specified format has reached v1.0.0, so the output times (v1.6.1 as of 12/31) --There are two types: server_generator that generates a controller and client_generator that generates TypeScript for API communication. --Basically just write the structure -(beta) Since godoc corresponding to swaggo is also inserted in the generated controller to some extent, Swagger code can be automatically generated if adjusted. -Repository (go-generalize/api_gen)
-I want to automatically generate an API controller/client with ** Go Code Justice ** ――It is troublesome to match the back-end and front-end I/F between engineers. --It's hard to write routing on the back end and API request on the front end for each implementation
Suitable for such people
Drop each binary
$ make bootstrap
Front-end code generation
$ cd frontend && make && cd -
Backend side code generation
$ cd backend && make && cd -
(By the way, cd -
returns to the previous directory / git checkout -
returns to the previous branch)
That's it for generation Success if you can confirm that various codes are generated under backend/and frontend / Details will be described later
{HTTP_METHOD}Name{Request|Response}
GET
, POST
, PUT
, DELETE
, PATCH
--The directory starts with _
/ service /: serviceID/hogehoge
, use /service/_serviceID/*.go
--The file starts with 0_
/service/0_service_id.go
if you want it to be /service/:serviceID
Run
$ server_generator ./hoge/
mock option (see below)
$ server_generator -mock ./hoge/
$ tree backend/interfaces
backend/interfaces
├── api
│ ├── user
│ │ ├── 0_user_id.go
│ │ ├── _userID
│ │ │ └── age_increment.go
│ │ └── search.go
│ └── user.go
└── health_check.go
3 directories, 5 files
GET /health_check
interfaces/health_check.go
type GetHealthCheckRequest struct{}
type GetHealthCheckResponse struct {
Status string `json:"status"`
}
PUT /api/user
interfaces/api/user.go
type PutUserRequest struct {
Name string `json:"name" validate:"required,min=3,max=10,excludesall=!()#@{}"`
Age int `json:"age" validate:"required,gt=0,lte=150"`
Gender model.Gender `json:"gender" validate:"required,oneof=1 2 3"`
}
type PutUserResponse struct {
Status int `json:"status"`
User *model.User `json:"payload,omitempty"`
}
GET /api/user/search
interfaces/api/user/search.go
type GetSearchRequest struct {
Name string `json:"name" query:"name"`
Age int `json:"age" query:"age"`
Gender model.Gender `json:"gender" query:"gender"`
}
type GetSearchResponse struct {
Status int `json:"status"`
User []*model.User `json:"payload,omitempty"`
Messages []werror.FailedReason `json:"messages"`
}
GET/PATCH/DELETE /api/user/:userID
interfaces/api/user/0_user_id.go
type GetRequest struct {
ID string `json:"userID" param:"userID" validate:"required"`
}
type GetResponse struct {
Status int `json:"status"`
User *model.User `json:"payload,omitempty"`
Messages []werror.FailedReason `json:"messages"`
}
type PatchRequest struct {
ID string `json:"userID" param:"userID" validate:"required"`
Name string `json:"name,omitempty" validate:"min=5,max=10,excludesall=!()#@{}"`
Age int `json:"age,omitempty" validate:"gt=0,lte=150"`
Gender model.Gender `json:"gender,omitempty" validate:"oneof=1 2 3"`
}
type PatchResponse struct {
Status int `json:"status"`
User *model.User `json:"payload,omitempty"`
Messages []werror.FailedReason `json:"messages"`
}
type DeleteRequest struct {
ID string `json:"userID" param:"userID" validate:"required"`
}
type DeleteResponse struct {
Status int `json:"status"`
Messages []werror.FailedReason `json:"messages"`
}
POST /api/user/:userID/age_increment
interfaces/api/user/_userID/age_increment.go
type PostAgeIncrementRequest struct {
ID string `json:"userID" param:"userID" validate:"required"`
}
type PostAgeIncrementResponse struct {
Status int `json:"status"`
User *model.User `json:"payload,omitempty"`
Messages []werror.FailedReason `json:"messages"`
}
$ make server_generate
$ tree backend/interfaces
backend/interfaces
├── api
│ ├── put_user_controller_gen.go
│ ├── routes_gen.go
│ ├── user
│ │ ├── 0_user_id.go
│ │ ├── _userID
│ │ │ ├── age_increment.go
│ │ │ ├── post_age_increment_controller_gen.go
│ │ │ └── routes_gen.go
│ │ ├── delete_controller_gen.go
│ │ ├── get_controller_gen.go
│ │ ├── get_search_controller_gen.go
│ │ ├── patch_controller_gen.go
│ │ ├── routes_gen.go
│ │ └── search.go
│ └── user.go
├── bootstrap_gen.go
├── get_health_check_controller_gen.go
├── health_check.go
├── props
│ └── controller_props.go
├── routes_gen.go
└── wrapper
├── internal
│ └── fmt.go
└── wrapper.go
6 directories, 20 files
interfaces/api/user/_userID/post_age_increment_controller_gen.go
// Package _userID ...
// generated version: 1.6.1
package _userID
import (
"github.com/54m/api_gen-example/backend/interfaces/props"
"github.com/labstack/echo/v4"
)
// PostAgeIncrementController ...
type PostAgeIncrementController struct {
*props.ControllerProps
}
// NewPostAgeIncrementController ...
func NewPostAgeIncrementController(cp *props.ControllerProps) *PostAgeIncrementController {
p := &PostAgeIncrementController{
ControllerProps: cp,
}
return p
}
// PostAgeIncrement ...
// @Summary WIP
// @Description WIP
// @Accept json
// @Produce json
// @Param userID path string true "user id"
// @Success 200 {object} PostAgeIncrementResponse
// @Failure 400 {object} wrapper.APIError
// @Failure 500 {object} wrapper.APIError
// @Router /api/user/{userID}/age_increment [POST]
func (p *PostAgeIncrementController) PostAgeIncrement(
c echo.Context, req *PostAgeIncrementRequest,
) (res *PostAgeIncrementResponse, err error) {
// API Error Usage: github.com/54m/api_gen-example/backend/interfaces/wrapper
//
// return nil, wrapper.NewAPIError(http.StatusBadRequest)
//
// return nil, wrapper.NewAPIError(http.StatusBadRequest).SetError(err)
//
// body := map[string]interface{}{
// "code": http.StatusBadRequest,
// "message": "invalid request parameter.",
// }
// return nil, wrapper.NewAPIError(http.StatusBadRequest, body).SetError(err)
panic("require implements.") // FIXME require implements.
}
--A library using Typescript + fetch is generated --Execute for the prepared folder for server_generator
After generation
$ tree frontend/client_generated
frontend/client_generated
├── api_client.ts
└── classes
├── api
│ ├── types.ts
│ └── user
│ ├── _userID
│ │ └── types.ts
│ └── types.ts
└── types.ts
4 directories, 5 files
import { APIClient } from "./client_generated/api_client";
// the simplest
(async () => {
const client = new APIClient();
const resp = await client.getHealthCheck(/* param */ {});
console.log(resp);
})();
// with options
(async () => {
const client = new APIClient(
/* token */
"pass", // [optional] token for Authorization: Bearer
/* commonHeaders */
{
"X-Foobar": "foobar", // [optional] custom headers
},
/* baseURL */
"http://localhost:8888", // [optional] custom endpoint
/* commonOptions */
{
cache: "no-cache", // [optional] options for fetch API
},
);
const resp = await client.api.putUser(
/* param */
{
name: "54m",
age: 100,
gender: 1,
},
/* header */
{
"X-Foobar": "hoge", // [optional] custom headers
},
/* options */
{
mode: "cors" // [optional] options for fetch API
},
);
console.log(resp);
})();
A. That's not the case A mock server is prepared for api_gen, and if used correctly, the back end and front end can be developed in parallel.
The sample prepared this time is treated as follows
$ make server_generate_with_mock
What did you do
$ server_generator -mock ./interfaces
Just add -mock
to the argument like this
mock_jsons/
mock_routes_gen.go
mock_bootstrap_gen.go
backend/interfaces/cmd/mock/main.go
These files are additionally automatically generated
$ make run_mock
The mock server is directly under the directory to be generated
Generated as cmd/mock/main.go
Build options to start the mock server
Must have -tags mock
go run -tags mock backend/interfaces/cmd/mock/main.go -port 8888
import { APIClient, MockOption } from "./client_generated/api_client";
// mock mode
(async () => {
const client = new APIClient();
const mockOption: MockOption = {
wait_ms: 10,
target_file: 'default.json'
}
const resp = await client.api.user.getSearch(/* param */ {
name: "54m",
age: 100,
gender: 1,
}, /* header */ undefined, /* options */ {
'mock_option': mockOption,
});
console.log(resp);
})();
Documents about mock server (server) / Documents about mock server (client)
A. Set {credentials:" include "}
as an option in the fetch API (FYI)
I was happy just because the API I/F was automatically generated (?) It's still under development, so we are waiting for Issue / PR!