rss twitter gitlab github linkedin linkedin instagram
Microservices in Go: REST APIs (Part 4) - OpenAPI 3 and Swagger UI
May 02, 2021

This post is part 4 in a series:

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:

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 ...

Conclusion

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.

If you’re looking to sink your teeth into more REST and Web Programming I recommend the following books:


Back to posts