Go Client Code Generation from Swagger and OpenAPI

Kyodo Tech
3 min readAug 7, 2023

--

If you’ve ever worked with modern web APIs, you’ve likely come across Swagger or OpenAPI specifications. These specifications have become an industry standard for describing RESTful APIs, and there’s a vast array of tools available for working with them. Building a Go client for an API described by these specifications is an everyday task. In this article we will briefly step through the process using oapi-codegen .

Why Automate Client Code Generation?

Automating client code generation has several benefits:

  • Consistency: Ensure that our client code is consistently generated based on the API specifications.
  • Efficiency: Save development time by automating a repetitive task.
  • Maintainability: Make future updates to the API client simpler by defining the generation logic in one place.
  • Collaboration: Enable team members to regenerate the client code without needing to know the specific steps or tools required.

Using go generate in Our Workflow

In our examples, we use go generate, a special Go command designed to automate code generation. By defining go:generate directives in code comments, we can set up a series of commands that are run when we execute go generate ./... in our project directory. This approach keeps our code generation steps documented and reproducible, and it’s an integral part of a maintainable codebase.

Generating Client Code

First, ensure we have oapi-codegen installed:

go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@latest

Depending on our situation, we might be dealing with Swagger specifications, OpenAPI v3.0 specifications, or a local file. Here’s how to handle each case:

Local File

If we have a local file containing the specifications, we can use it directly:

//go:generate oapi-codegen -package=examplepkg -generate=types,client,spec -o=examplepkg/example-client.go ./docs/openapi.json

Online OpenAPI v3.0 Specifications

If the specifications are already in OpenAPI v3.0 format and available online, we can download and use them directly:

//go:generate bash -c "mkdir -p examplepkg && curl -s https://www.example.org/api/openapi.yaml | oapi-codegen -package=examplepkg -generate=types,client,spec -o=examplepkg/example-client.go /dev/stdin"

Swagger Specifications — OpenAPI v2.0

If we have Swagger specifications, we will need to convert them to OpenAPI v3.0 first:

//go:generate bash -c "mkdir -p examplepkg && curl -s https://www.example.org/api/swagger.yaml | npx swagger2openapi -o /dev/stdout /dev/stdin | oapi-codegen -package=examplepkg -generate=types,client,spec -o=examplepkg/example-client.go /dev/stdin"

Understanding the Generated Client Boilerplate

When using oapi-codegen, it’s insightful to understand what’s generated:

  • Interface Specifications: Describes the methods representing the API operations.
  • Client Structure: Defines the client object, including server endpoint and HTTP client customization.
  • Function Handling: Handles the creation of client functions for specific body types.
  • Request Editing: Allows customization of requests through request editing callbacks.

Enhancing Flexibility with Customization Options

One of the highlights of oapi-codegen is the allowance for customization. We can pass in our own http.Client and use request editing callbacks to add headers or any additional requirements. These features empower developers to tailor the generated code to specific needs without losing the efficiency gained through automated code generation.

Adding Custom Headers for Authentication

Sometimes, our generated client needs to include authentication headers in every request. Here’s an example of how you can achieve this by creating a provider to handle custom headers:

package example

import (
"context"
"net/http"
)

const BaseURL = "https://api.example.org/"

type ExampleBearerToken struct {
token string
}

func NewExampleAuthProvider(token string) *ExampleBearerToken {
return &ExampleBearerToken{token: token}
}

func (s *ExampleBearerToken) Intercept(ctx context.Context, req *http.Request) error {
req.Header.Set("X-API-Key", s.token)
return nil
}

Using the Generated Client

Utilize the generated client code in our main application:

ackage main

import (
"context"
"fmt"
"log"
"os"
"github.com/kyodo-tech/go-example"
"github.com/kyodo-tech/go-example/examplepkg"
)

func main() {
token := os.Getenv("EXAMPLE_API_KEY")
provider := example.NewExampleAuthProvider(token)

client, err := examplepkg.NewClientWithResponses(
example.BaseURL,
examplepkg.WithRequestEditorFn(provider.Intercept),
)

// Use the client here
}

Conclusion

In summary, oapi-codegen offers:

  • Ease of use: Through automation and clear instructions.
  • Customization: With the ability to add custom headers and modify requests.
  • Philosophy of Simplicity: Balancing between simplicity and generic capabilities.

Understanding its intended use and limitations can help developers make the most of this tool, aligning it with their projects, and focusing on what truly matters: implementing business logic that drives innovation.

--

--