Building Microservices in Go: REST APIs - OpenAPI 3 and Swagger UI
Tags: go, openapi, swagger, tutorial
Published: 2021-06-19 22:25:16

## 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](https://swagger.io/blog/api-strategy/difference-between-swagger-and-openapi/), 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](https://github.com/MarioCarrion/todo-api-microservice-example/tree/074bbb9f4d0f79e5bced943c10c56013705969a9).

## Implementing OpenAPI 3

Using Swagger 2.0 in Go well-known to be supported by packages like [github.com/go-swagger/go-swagger](https://github.com/go-swagger/go-swagger) and [github.com/swaggo/swag](https://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](https://github.com/getkin/kin-openapi): to generate OpenAPI 3.0 documentation, and
* [github.com/deepmap/oapi-codegen](https://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](https://github.com/MarioCarrion/todo-api-microservice-example/blob/074bbb9f4d0f79e5bced943c10c56013705969a9/internal/rest/open_api.go).

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:

```go
// 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](https://github.com/MarioCarrion/todo-api-microservice-example/blob/074bbb9f4d0f79e5bced943c10c56013705969a9/cmd/openapi-gen/main.go) we call [`go generate`](https://github.com/MarioCarrion/todo-api-microservice-example/blob/074bbb9f4d0f79e5bced943c10c56013705969a9/internal/rest/open_api.go#L11) 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:

```go
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](https://github.com/MarioCarrion/todo-api-microservice-example/blob/074bbb9f4d0f79e5bced943c10c56013705969a9/internal/rest/open_api.go#L190-L206):

```go
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](https://github.com/swagger-api/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](https://github.com/swagger-api/swagger-ui/tree/master/dist) 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`](https://github.com/MarioCarrion/todo-api-microservice-example/tree/074bbb9f4d0f79e5bced943c10c56013705969a9/cmd/rest-server/static/swagger-ui), and then embedding those using the new `embed` package included in Go 1.16, using something like:

```go
//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](https://github.com/MarioCarrion/todo-api-microservice-example/blob/074bbb9f4d0f79e5bced943c10c56013705969a9/cmd/rest-server/static/swagger-ui/index.html#L42) 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](https://github.com/deepmap/oapi-codegen). The way it works is like this:

We install the generator:

```go
go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.5.1
```

Then using `go generate` when generate [our types](https://github.com/MarioCarrion/todo-api-microservice-example/blob/074bbb9f4d0f79e5bced943c10c56013705969a9/internal/rest/open_api.go#L12-L13):

```go
//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](https://github.com/MarioCarrion/todo-api-microservice-example/blob/074bbb9f4d0f79e5bced943c10c56013705969a9/cmd/cli/main.go):

```go
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:

* [The Design of Web APIs (2019)](https://amzn.to/32yFwFe)
* [Irresistible APIs (2016)](https://amzn.to/2MQHYmo)
* [Go Web Programming (2016)](https://amzn.to/30euJ1U)
* [REST in Practice (2010)](https://amzn.to/3n6ztRZ)