oapi-codegenoapi-codegen is a command-line tool and library to convert OpenAPI specifications to Go code, be it server-side implementations, API clients, or simply HTTP models.
Using oapi-codegen allows you to reduce the boilerplate required to create or integrate with services based on OpenAPI 3.0, and instead focus on writing your business logic, and working on the real value-add for your organisation.
With oapi-codegen, there are a few Key Design Decisions we've made, including:
oapi-codegen is one part of a wider ecosystem, which can be found described in further detail in the oapi-codegen organisation on GitHub.
⚠️ This README may be for the latest development version, which may contain unreleased changes. Please ensure you're looking at the README for the latest release version.
As announced in May 2024,
we have moved the project from the deepmap organization to our own organization, and you will need to update your
import paths to pull updates past this point. You need to do a recursive search/replace from
github.com/deepmap/oapi-codegen/v2 to github.com/oapi-codegen/oapi-codegen/v2.
[!IMPORTANT]
oapi-codegenmoved to its new home with the version tagv2.3.0.
If you are using v2.2.0 or below, please install like so:
# for the binary install
go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@v2.2.0
If you are using v2.3.0 or above, please install like so, using the new module import path:
# for the binary install
go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest
It is recommended to follow the go tool support available from Go 1.24+ for managing the dependency of oapi-codegen alongside your core application.
To do this, you run go get -tool:
$ go get -tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest
# this will then modify your `go.mod`
From there, each invocation of oapi-codegen would be used like so:
//go:generate go tool oapi-codegen -config cfg.yaml ../../api.yaml
Alternatively, you can install it as a binary with:
$ go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest
$ oapi-codegen -version
Which then means you can invoke it like so:
//go:generate oapi-codegen --config=config.yaml ../../api.yaml
Note that you can also move your tools.go into its own sub-module to reduce the impact on your top-level go.mod.
While the project does not (yet) have a defined release cadence, there may be cases where you want to pull in yet-unreleased changes to your codebase.
Therefore, you may want to pin your dependency on oapi-codegen to a given commit hash, rather than a tag.
This is officially recommended for consumers of oapi-codegen, who want features/bug fixes that haven't yet been released.
We aim to keep the default branch ready-to-release so you should be able to safely pin.
To do so, you can run:
# pin to the latest version on the default branch
$ go get github.com/oapi-codegen/oapi-codegen/v2@main
# alternatively, to a commit hash i.e. https://github.com/oapi-codegen/oapi-codegen/commit/71e916c59688a6379b5774dfe5904ec222b9a537
$ go get github.com/oapi-codegen/oapi-codegen/v2@71e916c59688a6379b5774dfe5904ec222b9a537
This will then make a change such as:
diff --git go.mod go.mod
index 44f29a4..436a780 100644
--- go.mod
+++ go.mod
@@ -2,21 +2,20 @@
-require github.com/oapi-codegen/oapi-codegen/v2 v2.1.0
+require github.com/oapi-codegen/oapi-codegen/v2 v2.1.1-0.20240331212514-80f0b978ef16
oapi-codegen is largely configured using a YAML configuration file, to simplify the number of flags that users need to remember, and to make reading the go:generate command less daunting.
For full details of what is supported, it's worth checking out the GoDoc for codegen.Configuration.
We also have a JSON Schema that can be used by IDEs/editors with the Language Server Protocol (LSP) to perform intelligent suggestions, i.e.:
# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/v2.6.0/configuration-schema.json
package: api
# ...
Note that it's recommended to pin to a specific version of the configuration schema, so it matches the version of oapi-codegen you're using. For instance, if you're using Renovate, you can have Renovate automagically update this version for you.
Although we strive to retain backwards compatibility - as a project that's using a stable API per SemVer - there are sometimes opportunities we must take to fix a bug that could cause a breaking change for people relying upon the behaviour.
In this case, we will expose a compatibility option to restore old behaviour.
At a high level, oapi-codegen supports:
Below we can see a trimmed down example taken from the OpenAPI Petstore example:
// generated code
type ServerInterface interface {
// ...
// Returns all pets
// (GET /pets)
FindPets(w http.ResponseWriter, r *http.Request, params FindPetsParams)
// ...
}
// FindPets operation middleware
func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Request) {
var err error
// Parameter object where we will unmarshal all parameters from the context
var params FindPetsParams
// ------------- Optional query parameter "tags" -------------
err = runtime.BindQueryParameter("form", true, false, "tags", r.URL.Query(), ¶ms.Tags)
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "tags", Err: err})
return
}
// ------------- Optional query parameter "limit" -------------
err = runtime.BindQueryParameter("form", true, false, "limit", r.URL.Query(), ¶ms.Limit)
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "limit", Err: err})
return
}
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.FindPets(w, r, params)
}))
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
}
// HandlerWithOptions creates http.Handler with additional options
func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler {
m := options.BaseRouter
if m == nil {
m = http.NewServeMux()
}
if options.ErrorHandlerFunc == nil {
options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}
wrapper := ServerInterfaceWrapper{
Handler: si,
HandlerMiddlewares: options.Middlewares,
ErrorHandlerFunc: options.ErrorHandlerFunc,
}
m.HandleFunc("GET "+options.BaseURL+"/pets", wrapper.FindPets)
return m
}
Then, in your own code, you implement the underlying logic for the FindPets implementation:
type PetStore struct {
Pets map[int64]Pet
NextId int64
Lock sync.Mutex
}
// Make sure we conform to ServerInterface
var _ ServerInterface = (*PetStore)(nil)
func NewPetStore() *PetStore {
return &PetStore{
Pets: make(map[int64]Pet),
NextId: 1000,
}
}
// FindPets implements all the handlers in the ServerInterface
func (p *PetStore) FindPets(w http.ResponseWriter, r *http.Request, params FindPetsParams) {
p.Lock.Lock()
defer p.Lock.Unlock()
var result []Pet
for _, pet := range p.Pets {
if params.Tags != nil {
// If we have tags, filter pets by tag
for _, t := range *params.Tags {
if pet.Tag != nil && (*pet.Tag == t) {
result = append(result, pet)
}
}
} else {
// Add all pets if we're not filtering
result = append(result, pet)
}
if params.Limit != nil {
l := int(*params.Limit)
if len(result) >= l {
// We're at the limit
break
}
}
}
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(result)
}
As we can see, oapi-codegen simplifies some of the boilerplate by taking parameters out of the request and instead allows us to focus on the implementation.
You'll note that there's still a bit more marshaling of request/response data, which is further reduced by using the Strict server functionality.
When using the strict server, you'll have the following generated code:
// StrictServerInterface represents all server handlers.
type StrictServerInterface interface {
// ...
// Returns all pets
// (GET /pets)
FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
// ...
}
func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface {
return &strictHandler{ssi: ssi, middlewares: middlewares, options: options}
}
// FindPets operation middleware
func (sh *strictHandler) FindPets(w http.ResponseWriter, r *http.Request, params FindPetsParams) {
var request FindPetsRequestObject
request.Params = params
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) {
return sh.ssi.FindPets(ctx, request.(FindPetsRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "FindPets")
}
response, err := handler(r.Context(), w, r, request)
if err != nil {
sh.options.ResponseErrorHandlerFunc(w, r, err)
} else if validResponse, ok := response.(FindPetsResponseObject); ok {
if err := validResponse.VisitFindPetsResponse(w); err != nil {
sh.options.ResponseErrorHandlerFunc(w, r, err)
}
} else if response != nil {
sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response))
}
}
Then, in your own code, you implement the underlying logic for the FindPets implementation:
// Make sure we conform to StrictServerInterface
var _ StrictServerInterface = (*PetStore)(nil)
func NewPetStore() *PetStore {
return &PetStore{
Pets: make(map[int64]Pet),
NextId: 1000,
}
}
// FindPets implements all the handlers in the ServerInterface
func (p *PetStore) FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error) {
p.Lock.Lock()
defer p.Lock.Unlock()
var result []Pet
for _, pet := range p.Pets {
if request.Params.Tags != nil {
// If we have tags, filter pets by tag
for _, t := range *request.Params.Tags {
if pet.Tag != nil && (*pet.Tag == t) {
result = append(result, pet)
}
}
} else {
// Add all pets if we're not filtering
result = append(result, pet)
}
if request.Params.Limit != nil {
l := int(*request.Params.Limit)
if len(result) >= l {
// We're at the limit
break
}
}
}
return FindPets200JSONResponse(result), nil
}
We can see that this provides the best means to focus on the implementation of the business logic within the endpoint, rather than (un)marshalling types to and from JSON, or wrangling cookies or headers.
text/templates, which are user-overridableadditionalProperties are ignored by default (more details)oapi-codegen shines by making it fairly straightforward (note that this is a purposeful choice
$ claude mcp add oapi-codegen \
-- python -m otcore.mcp_server <graph>