SkillAgentSearch skills...

Apigen

HTTP API Layer Generator for the Go (golang) projects

Install / Use

/learn @gemyago/Apigen
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

apigen - HTTP API Layer Generator

Test Golang Coverage

HTTP API Layer Generator for the Go (golang) projects. Write less boilerplate code, focus on the business logic.

Features:

  • OpenAPI first approach. Write the spec and generate the code.
  • No runtime dependencies. Generated code is self-contained.
  • No reflection. Code to parse and validate requests is fully generated.
  • Framework agnostic and http.Handler compatible.

Project status:

  • The generated code is extensively tested and production ready
  • The generator itself is in beta stage. This means that minor breaking changes in the generated code may occur.

Table of Contents

Getting Started

The only runtime dependency is a go 1.24 or higher. The generator is a plugin for the OpenAPI Generator which is Java based and requires Java 11 runtime at a minimum. The java and openapi-generator are only required to generate the code. The generated code is self-contained and does not have any runtime dependencies.

To get started, install apigen cli tool:

go install github.com/gemyago/apigen

Define the OpenAPI spec somewhere in your project. For example: internal/api/http/routes.yaml. You can use below as a starting point:

openapi: "3.0.0"
info:
  version: 1.0.0
  title: Minimalistic API definition
paths:
  /ping:
    get:
      operationId: ping
      tags:
        - ping
      parameters:
        - name: message
          in: query
          required: false
          schema:
            type: string
      responses:
        '200':
          description: Request succeeded
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string

Add a golang file with generation instructions. For example: internal/api/http/routes.go:

//go:generate go run github.com/gemyago/apigen server ./routes.yaml ./routes

Run the generation:

go generate ./internal/api/http

The above will generate the code in the internal/api/http/routes folder. Commit the generated code to the repository.

Declare controller that implements the generated interface, for example:

func pingHandler(_ context.Context, params *models.PingParams) (*models.Ping200Response, error) {
	message := params.Message
	if message == "" {
		message = "pong"
	}
	return &models.Ping200Response{Message: message}, nil
}

type pingController struct{}

func (c *pingController) Ping(b handlers.HandlerBuilder[
	*models.PingParams,
	*models.Ping200Response,
]) http.Handler {
	return b.HandleWith(pingHandler)
}

Define router adapter. For example http.ServeMux adapter may look like this:

type httpRouter http.ServeMux

func (*httpRouter) PathValue(r *http.Request, paramName string) string {
	return r.PathValue(paramName)
}

func (router *httpRouter) HandleRoute(method, pathPattern string, h http.Handler) {
	(*http.ServeMux)(router).Handle(method+" "+pathPattern, h)
}

func (router *httpRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	(*http.ServeMux)(router).ServeHTTP(w, r)
}

Wire everything together:

rootHandler := handlers.NewRootHandler((*httpRouter)(http.NewServeMux()))
rootHandler.RegisterPingRoutes(&pingController{})

const readHeaderTimeout = 5 * time.Second
srv := &http.Server{
	Addr:              "[::]:8080",
	ReadHeaderTimeout: readHeaderTimeout,
	Handler:           rootHandler,
}
log.Println("Starting server on port: 8080")
if err := srv.ListenAndServe(); err != nil {
	panic(err)
}

Fully functional example based on the above steps can be found here. More advanced examples:

Basic Concepts

Generated code expects you to provide a controller that implements the generated interface. The controller is an adapter between the generated code and your business logic. Generated code will parse the request, validate parameters and call the corresponding controller method in a type-safe manner.

The controller is generated based on the tags in the OpenAPI spec. Prefer defining a single tag per operation. You can have multiple operations with a same tag in order to group them under the same generated controller. Single OpenAPI spec can define as many tags (controllers) as needed. Please note that operationIDs in OpenAPI are global and should be unique across the spec. Please see the Controllers in depth section for more details.

The generated code also includes so called RootHandler. The root handler is a bridge between your router of choice and the generated code. Please see the Root Handler section for more details.

Typically you will need to import generated code from the following packages:

  • handlers contains controller interfaces, root handler and other components handle requests.
  • models contains data structures corresponding to schemas defined in the OpenAPI spec.

It is possible to generate models and controllers in separate packages. This is useful when you want to keep your application layer separate from the HTTP API layer. Please see the Separate packages for models and controllers section for more details.

Controllers in depth

Controller should implement a set of methods, each corresponding to an operation in the OpenAPI spec. The method signature is as follows:

func (c *PetsController) GetPetByID(
	b handlers.HandlerBuilder[*models.GetPetByIDParams, *models.PetResponse],
) http.Handler {
	// Your implementation here
}

The HandlerBuilder allows you to create an actual http.Handler that will be used to process requests. In most simplest case you can implement the method in place. However in real-world scenarios you may want to extract the implementation to a separate component and keep your controller clean and declarative. It is not required to use the HandlerBuilder and you can return a http.Handler directly if your use-case requires it, this allows you to fully bypass the generated code and handle the request processing as required.

The HandlerBuilder has the following methods:

  • HandleWith - will bind your application logic to the generated code. The handler function should have the following signature:

    func(context.Context, *models.GetPetByIDParams) (*models.PetResponse, error)
    

    This would usually be the most typical way to implement the controller method. You can define the handler in place however it is advised have a separate component that implements the handler function. This approach will help you to keep your controller clean and declarative.

  • HandleWithHTTP - similar to the above, but allows you to access the underlying http.Request and http.ResponseWriter. The handler function should have the following signature:

    func(http.ResponseWriter, *http.Request, *models.GetPetByIDParams)  (*models.PetResponse, error)
    

    This method is useful when you need to access the underlying http request and response objects. For example, when you need to set custom headers or status codes.

    Notes:

    • You may return response that will be automatically written to the response writer.
    • The generated code will not attempt to write to the response writer if you have already written to it.
    • You may still return an error. In this case the generated code will handle the error as explained in the Handling errors section.

Due to Go language constraints there are several variations of the HandlerBuilder. The variations are:

  • NoResponseHandlerBuilder - for operations that do not return a response. The handler function should have the following signature:
    func(context.Context, *models.DeletePetParams) error
    
  • NoRequestHandlerBuilder - for operations that do not have request parameters. The handler function should have the following signature:
    func(context.Context) (*models.PetResponse, error)
    
  • NoRequestNoResponseHandlerBuilder - for operations that do not have request parameters and do not return a response. The handler function should have the following signature:
    func(context.Context) error
    

The generated code is type safe so you will catch mismatches at compile time.

Root Handler

The root handler is an adapter that allows you to attach generated routes to a router of your choice. Once initialized, the root handler is a self contained http.Handler and can be used in any scenario where you would use a standard http handler.

The root handler will have Register[Controller]Routes methods generated for each controller of your APIs. The method will accept an instance of the controller and will register all routes defined in the OpenAPI spec. Example:

rootHandler := handlers.New

Related Skills

View on GitHub
GitHub Stars6
CategoryDevelopment
Updated4mo ago
Forks0

Languages

Go

Security Score

87/100

Audited on Nov 13, 2025

No findings