Mario Carrion
Posted on June 19, 2021
What is Swagger? OpenAPI?
Swagger/OpenAPI allows us to document and collaborate with our users, specifically it allows to define the resources, parameters, types, fields and everything that describes the APIs we are building. Swagger and OpenAPI are two different things, it's better explained on the original blog post, but the idea is basically this:
- OpenAPI: Specification
- Swagger: Tools for implementing the specification
Disclaimer: This post includes Amazon affiliate links. If you click on one of them and you make a purchase I'll earn a commission. Please notice your final price is not affected at all by using those links.
The code used for this post is available on Github.
Implementing OpenAPI 3
Using Swagger 2.0 in Go well-known to be supported by packages like github.com/go-swagger/go-swagger and github.com/swaggo/swag however it gets more complicated when trying to use something much more recent like OpenAPI 3, in those cases really we have a few packages we can use:
- github.com/getkin/kin-openapi/openapi3: to generate OpenAPI 3.0 documentation, and
- github.com/deepmap/oapi-codegen: to generate Go client and server boilerplate from OpenAPI 3 specifications.
For generating the OpenAPI 3.0 document, we are going to be using the getkin/kin-openapi/openapi3
package, our example already defines a function that does that for us.
This function is long and it could a turn off for a lot of people, however I like it the way it is because it allows me to explicitly indicate the values I need, there's no magic (like in go-swagger
or swagger/swag
for example), it's a pro/con depending on how you see it because everything it's more manual.
In the end, the code needed to represent our API will be equivalent to the code written to build that structure. For example, taking the basic OpenAPI details, it looks like:
// NewOpenAPI3 instantiates the OpenAPI specification for this service.
func NewOpenAPI3() openapi3.Swagger {
swagger := openapi3.Swagger{
OpenAPI: "3.0.0",
Info: &openapi3.Info{
Title: "ToDo API",
Description: "REST APIs used for interacting with the ToDo Service",
Version: "0.0.0",
License: &openapi3.License{
Name: "MIT",
URL: "https://opensource.org/licenses/MIT",
},
Contact: &openapi3.Contact{
URL: "https://github.com/MarioCarrion/todo-api-microservice-example",
},
},
Servers: openapi3.Servers{
&openapi3.Server{
Description: "Local development",
URL: "http://0.0.0.0:9234",
},
},
}
// ... more code ...
With that function defined and with a helper binary we call go generate
to build the YAML and JSON files we need to describe our API.
In practice the code in that cmd/openapi-gen/main.go
looks basically like this:
func main() {
swagger := rest.NewOpenAPI3()
// openapi3.json
data, _ := json.Marshal(&swagger)
_ = os.WriteFile(path.Join(output, "openapi3.json"), data, 0644) // XXX: explicitly ignoring errors
// openapi3.yaml
data, _ = yaml.Marshal(&swagger)
_ = os.WriteFile(path.Join(output, "openapi3.yaml"), data, 0644) // XXX: explicitly ignoring errors
}
Last thing is to make those files available:
func RegisterOpenAPI(r *mux.Router) {
swagger := NewOpenAPI3()
r.HandleFunc("/openapi3.json", func(w http.ResponseWriter, r *http.Request) {
// ... code here ...
}).Methods(http.MethodGet)
r.HandleFunc("/openapi3.yaml", func(w http.ResponseWriter, r *http.Request) {
// ... code here ...
}).Methods(http.MethodGet)
}
Implementing Swagger UI
We built out OpenAPI 3 files, now as part of our HTTP handlers we are going to allow our users to interact with the Swagger UI to invoke our API, this is something that you may or not want to have in production, I personally prefer only allowing internal environments to support this UI for testing purposes.
To support the Swagger UI our your API we need to download all the dist
files from the original repository and then embed them as part of your API as a new handler, for example copying the files over to cmd/rest-server/static/swagger-ui
, and then embedding those using the new embed
package included in Go 1.16, using something like:
//go:embed static
var content embed.FS
func main() {
// ... other code ...
r := mux.NewRouter()
fsys, _ := fs.Sub(content, "static")
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.FS(fsys))))
srv := &http.Server{
Handler: r,
// ... other code ...
}
log.Fatal(srv.ListenAndServe())
}
With that code in place we will be able to request http://address:port/static/swagger-ui
and load our API using the OpenAPI 3 file we generated previously, one really important thing to change is the index.html
file to refer to the local openapi3.json
file and perhaps you want to generate that file depending on environment so it always points to the right HTTP URL.
Generating Client and Serve code from OpenAPI 3
Another interesting thing we can do after generating our OpenAPI 3 documentation is to generate boilerplate that represents Go types matching the original API, for that we use a package we mentioned earlier: github.com/deepmap/oapi-codegen. The way it works is like this:
We install the generator:
go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.5.1
Then using go generate
when generate our types:
//go:generate oapi-codegen -package openapi3 -generate types -o ../../pkg/openapi3/task_types.gen.go openapi3.yaml
//go:generate oapi-codegen -package openapi3 -generate client -o ../../pkg/openapi3/client.gen.go openapi3.yaml
Which in our example will create a bunch of types in the pkg
package that then we can use to programmatically build some code to interact with out API, like the following example:
func main() {
client, _ := openapi3.NewClientWithResponses("http://0.0.0.0:9234") // XXX: explicitly ignoring errors
priority := openapi3.Priority_low
respC, _ := client.CreateTaskWithResponse(context.Background(), // XXX: explicitly ignoring errors
openapi3.CreateTaskJSONRequestBody{
Dates: &openapi3.Dates{
Start: newPtrTime(time.Now()),
Due: newPtrTime(time.Now().Add(time.Hour * 24)),
},
Description: newPtrStr("Sleep early"),
Priority: &priority,
})
// ... other code ...
Parting words
Documenting REST APIs in Go using Swagger 2.0 is relatively well supported and easy to do, there are a few different alternatives, however the concern you may have is that you're depending on technologies that are already old, if you want to explore the most recent alternatives like OpenAPI 3 things get more complicated, because the options are much more manual and really state of the art, which is a fair concern for people looking for long term solutions.
So what's the best approach? The way I see it is, if you're not willing to invest resources trying out new state-of-the-art tools then using Swagger 2.0 is your answer; however if you're willing to contribute back to the community exploring the OpenAPI 3 options is better because in the end it benefits everybody.
Recommended Reading
If you're looking to sink your teeth into more REST and Web Programming I recommend the following books:
Posted on June 19, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.