January 4, 2018

Create Golang API documentation with SwaggerUI

Having a documentation for you your APIs is more useful than you might be thinking. Even if you don’t expose your APIs publicly, having the docs for your frontend or mobile team is way easier than sending them screenshots/snippets or using a paid product like Postman/Insomnia (premium version with syncing). With SwaggerUI, you can get a well-designed documentation for all your APIs automatically. When moving to Go I had issues setting it up, due to lacking documentation/tutorials, so I decided to write one.

Example repo: LINK

I’ve come across SwaggerUI about two years ago while working on a restful backend for an enterprise application. SmartBear, the creator of SwaggerUI, describes its product as:

“Swagger UI allows anyone — be it your development team or your end consumers — to visualize and interact with the API’s resources without having any of the implementation logic in place. It’s automatically generated from your Swagger specification, with the visual documentation making it easy for back-end implementation and client-side consumption.”

In short, by providing a Swagger (OpenAPI) specification, you can get the interface for interacting with your APIs, without caring about the programming language itself. Think of Swagger (OpenAPI) as WSDL for REST.

For reference, from that specification, Swagger Codegen lets you generate client and server in dozens of programming languages.

Back at that time, I was working with Java and SpringBoot, and Swagger felt really easy to use. You create a bean once, and add one or two annotations for your endpoints, adding a title and a description of your project. Additionally, I used to redirect all requests from “/” to “/swagger-ui” so that opening the host:port would automatically redirect to SwaggerUI. By running the application, SwaggerUI was available on the same port (e.g. your application is running on host:port, SwaggerUI will be available on host:port/swagger-ui).

Fast forward year and a half, I wanted to implement SwaggerUI in our Go projects. The problem was - it felt too complicated. And as I searched the internet, I saw that it wasn’t only me. Many other users had same troubles as I did.

I stumbled upon Go-Swagger project. Their GitHub Readme talks about Swagger, not SwaggerUI. You have a client, server, middleware and somewhere at the end, SwaggerUI is mentioned. In short, Swagger server/client is used to generate (backend) code from your Swagger definition (swagger.json). Generating server lets you serve the APIs from the spec while generating a client produces a consumer for those APIs. Think of it as code generation tool. I find it quite useful, but that was not what I wanted.

With help of the community (shoutout to casualjim) and a bit of investigation, I managed to produce API Documentation for our project without much boilerplate code.

In addition, I prepared an example repo where I implemented go-swagger annotations to produce valid swagger documentation, available HERE.

Installing Go-Swagger

Before getting started with implementation, you need to install goswagger on your local machine. This is not a mandatory step but will make working with swagger much easier. Installing it lets you test your annotations locally, otherwise, you’d have to rely on your CI tool only.

The easiest way to install it is via Homebrew/Linuxbrew by running:

brew tap go-swagger/go-swagger
brew install go-swagger

Otherwise, you can get the latest binary from here.

Swagger:meta [docs]

This is the first annotation you should add to your project. It is used to describe your project title, description, contact email, website, license and much more.

If your API is served ONLY on HTTP or HTTPS, produces ONLY JSON you should add it here - allowing to you to remove that annotation from every single route.

Security is also to be added in swagger:meta, adding an Authorize button on SwaggerUI. To implement JWT, I named by Security type bearer and defined it like:

//     Security:
//     - bearer
//
//     SecurityDefinitions:
//     bearer:
//          type: apiKey
//          name: Authorization
//          in: header
//

Swagger:route [docs]

There are two ways two annotate your routes, swagger:operation and swagger:route. Both seem pretty similar, so what is the main difference?

Think of swagger:route as a short annotation for simple APIs. It is used for APIs that don’t have input parameters (path/query parameters). An example of those (with paramaters) is /repos/{owner}, /user/{id} or /users/search?name=ribice

If you have one of those, then you must use swagger:operation. Otherwise, APIs like /user or /version can be annotated with swagger:route.

swagger:route annotation contains the following:

// swagger:route POST /repo repos users createRepoReq
// Creates a new repository for the currently authenticated user.
// If repository name is "exists", error conflict (409) will be returned.
// responses:
//  200: repoResp
//  400: badReq
//  409: conflict
//  500: internal
  1. swagger:route - Annotation

  2. POST - HTTP method

  3. /repo - Path pattern, endpoint

  4. repos - Space separated tags under which route will be located, for example, “repos users”

  5. createRepoReq - Request which will be used for this endpoint (details will be explained later on)

  6. Creates a new repository … - Summary (title). For swagger:route annotation, text before first full stop (.) will be the title. If there is no full stop, there will be no title and the text provided will be used as the description.

  7. If repository name exists … - Description. For swagger:route annotation, text after first full stop (.) will be the description.

  8. responses: - This endpoint’s responses

  9. 200: repoResp - One of the (success) responses is HTTP Status 200, containing repoResp (a model annotated with swagger:response)

  10. 400: badReq, 409: conflict, 500: internal - Error responses for this endpoint (badReq, conflict and internal are defined under cmd/api/swagger/model.go)

Annotating your endpoint like this will produce the following:

Keep in mind that there are other annotations that you may need to use depending on your APIs. Since I defined my project to use single schema only (https), and all my APIs are using https, I don’t need to annotate schemes separately. If you are using multiple schemas for your endpoints, you need the following annotation(s):

// Schemes: http, https, ws, wss

Same applies for consuming/producing media types. All my APIs are consuming/producing only application/json. If your APIs are consuming/producing something else, you need to annotate them with that media type. For example:

// consumes:
// - application/json
// - application/x-protobuf
//
// produces:
// - application/json
// - application/x-protobuf

And security:

// security:
//   api_key:
//   oauth: read, write
//   basicAuth:
//      type: basic
//   token:
//      type: apiKey
//      name: token
//      in: query
//   accessToken:
//      type: apiKey
//      name: access_token
//      in: query

On the other hand, swagger:operation is used for more complex endpoints. The part underneath the three dashes (—) is parsed as YAML, allowing more complex annotations. Make sure your indentation is consistent and correct, as it won’t parse correctly otherwise.

Swagger:operation [docs]

Using swagger:operation annotations gets you access to all of OpenAPI specifications, letting you describe your complex endpoints. If you are interested in details, you can read the specification docs.

In short - swagger:operation contains the following:

// swagger:operation GET /repo/{author} repos repoList
// ---
// summary: List the repositories owned by the given author.
// description: If author length is between 6 and 8, Error Not Found (404) will be returned.
// parameters:
// - name: author
//   in: path
//   description: username of author
//   type: string
//   required: true
// responses:
//   "200":
//     "$ref": "#/responses/reposResp"
//   "404":
//     "$ref": "#/responses/notFound"
  1. swagger:operation - Annotation

  2. GET - HTTP method

  3. /repo/{author} - Path pattern, endpoint

  4. repos - Space separated tags under which route will be located, for example, “repos users”

  5. repoList - Request which will be used for this endpoint. This one does not exist (is not defined), but the parameter is mandatory so you can put anything instead of repoList (noReq, emptyReq etc.)

  6. - The part below this is the swagger spec in YAML. Make sure your indentation is consistent and correct, as it won’t parse correctly otherwise. Note that if you specify the tags, summary, description or operationId as part of the YAML spec, you will override the summary, descriptions, tags or operationId, specified as part of the regular swagger syntax above.

  7. summary: - Title

  8. description: - Description

  9. parameters: - URL Parameter (in this case {author}). Type is string, it is mandatory (swagger won’t let you invoke the endpoint without typing it in), and it is located in the path (/{author}). The alternative to this is in:query for query params (?name="")

After defining your routes, you need to define your requests and responses. From the example repo, you can see that I created a new package for this, named swagger. This is not mandatory, but it puts all the boilerplate code in a single package named swagger. The downside of this is that you have to export your HTTP requests and responses.

If you do create a separate Swagger package, make sure to import it in your main/server file (you can do this by putting an underscore before the import):

_ "github.com/ribice/golang-swaggerui-example/cmd/swagger"

Swagger:parameters [docs]

Depending on your application model, your HTTP request may vary (simple, complex, wrapped etc.). To generate Swagger specs you’ll need to create a struct for every different request, even simple ones containing a number only (id for example) or string (name).

Once you have such structs (for example a struct containing a string and a boolean value), in your Swagger package define the following:

1 // Request containing string
2 // swagger:parameters createRepoReq
3 type swaggerCreateRepoReq struct {
4 // in:body
5 api.CreateRepoReq
6 }
  • Line 1 contains a comment that will be visible on SwaggerUI

  • Line 2 contains swagger:parameters annotation, as well as the name (operationID) of the request. This name is used as the last parameter on routing annotations, to define the request.

  • Line 4 contains the position of this parameter (in:body, in:query etc.)

  • Line 5 is actual struct embedding. As mentioned earlier you don’t need a separate package for swagger annotations (you could put the swagger:parameters annotation on api.CreateRepoReq), but once you start creating response annotations and validations, it will be much cleaner to keep swagger related annotations in a separate package.

If you have large request, like create or update, instead of struct embedding you should create a new variable of that type. For example (notice the difference in 5th line):

1 // Request containing string
2 // swagger:parameters createRepoReq
3 type swaggerCreateRepoReq struct {
4 // in:body
5 Body api.CreateRepoReq
6 }

This produces the following SwaggerUI request:

There are lots of validation annotations for swagger:parameters and swagger:response, with detailed description and how-to usage in the docs linked next to annotation titles.

Swagger:response [docs]

Response annotation is very similar to parameters annotation. The main difference is that quite often, responses are wrapped into more complex structs, so you have to account for that in swagger.

In my example repo, my success responses look like:

{
   "code":200, // Code containing HTTP status CODE
   "data":{} // Data containing actual response data
}

While error responses are a bit different:

{
   "code":400, // Code containing HTTP status CODE
   "message":"" // String containing error message
}

To get started with common responses, such as error ones, I usually create model.go (or swagger.go) inside swagger package and define them inside. In example repo, the following response is used for OK responses (returning no data):

1 // Success response
2 // swagger:response ok
3 type swaggScsResp struct {
4     // in:body
5     Body struct {
6        // HTTP status code 200 - OK
7        Code int `json:"code"`
8    }
9 }

For error responses, most of them are similar to each other except for the name (and the comment on HTTP code in case of example repo). Nevertheless, you should still make structs for every error case, in order to put them as possible responses for your endpoints:

 1 // Error Forbidden
 2 // swagger:response forbidden
 3 type swaggErrForbidden struct {
 4    // in:body
 5    Body struct {
 6        // HTTP status code 403 -  Forbidden
 7        Code int `json:"code"`
 8        // Detailed error message
 9        Message string `json:"message"`
10    }
11}

Sample response with model.Repository in data:

 1// HTTP status code 200 and repository model in data
 2// swagger:response repoResp
 3type swaggRepoResp struct {
 4    // in:body
 5    Body struct {
 6        // HTTP status code 200/201
 7        Code int `json:"code"`
 8        // Repository model
 9        Data model.Repository `json:"data"`
10    }
11}

Sample response with slice of model.Repository in data:

 1// HTTP status code 200 and an array of repository models in data
 2// swagger:response reposResp
 3type swaggReposResp struct {
 4    // in:body
 5    Body struct {
 6        // HTTP status code 200 - Status OK
 7        Code int `json:"code"`
 8        // Array of repository models
 9        Data []model.Repository `json:"data"`
10    }
11}

In short, that will be enough to generate your API docs. You should add validations to your docs too, but following this guide will get you started. Since this is mostly composed out of my own experience, and to some extent looking at Gitea’s source code, I’ll be listening to feedback on how to improve this part and update it accordingly.

In case you have some issues or questions, I suggest checking the spec generation FAQ.

Running SwaggerUI locally

Once your annotations are ready, you’re most likely going to test it in your local environment. To do so, you need to run two commands:

  1. Generate spec [docs]

  2. Serve [docs]

The command we use to generate swagger.json and serve it with SwaggerUI:

swagger generate spec -o ./swagger.json --scan-models
swagger serve -F=swagger swagger.json

Or, if you want to make it a single command only:

swagger generate spec -o ./swagger.json --scan-models && swagger serve -F=swagger swagger.json

Once the command is executed, a new tab will be opened with SwaggerUI hosted at Petstore. The server has CORS enabled and appends the URL for the spec JSON to the petstore URL as a query string.

Alternatively, if you use the Redoc flavor (-F=redoc), the documentation will be hosted on your own machine (localhost:port/docs).

Deploying on server

There are many ways to deploy the generated SwaggerUI on a server. Once you have your swagger.json generated it should be relatively easy to serve it.

The easiest way is serving static files via Golang server. More on that is available HERE.

Another way, our app is running on Google App Engine. The Swagger Spec is generated by our CI tool and served on /docs route.

We deployed SwaggerUI as a Docker service on GKE (Google Container/Kubernates Engine), that picks up the swagger.json from the /docs route.

Part of our CI (Wercker) script:

build:
  steps:
    - script:
      name: workspace setup
      code: |
        mkdir -p $GOPATH/src/github.com/orga/repo
        cp -R * $GOPATH/src/github.com/orga/repo/
    - script:
      cwd: $GOPATH/src/bitbucket.org/orga/repo/cmd/api/
      name: build
      code: |
        go get -u github.com/go-swagger/go-swagger/cmd/swagger
        swagger generate spec -o ./swagger.json --scan-models
        CGO_ENABLED=0 go build -a -ldflags '-s' -installsuffix cgo -o app .
        cp app *.template Dockerfile swagger.json "$WERCKER_OUTPUT_DIR"

Routing:

func (d *Doc) docHandler(c context.Context, w http.ResponseWriter, r *http.Request) {
   r.Header.Add("Content-Type", "application/json")
   data, _ := ioutil.ReadFile("/swagger.json")
   w.Write(data)
}

Dockerfile:

FROM swaggerapi/swagger-ui
ENV API_URL "https://api.orga.com/swagger"

Summary

SwaggerUI is a powerful API documentation tool, that lets you easily and beautifully document your APIs. With help of go-swagger project, you can easily produce a swagger specification file (swagger.json) needed for SwaggerUI.

In short, I described the steps I took in order to achieve this. There might be better ways, and I’ll make sure to update this article according to the received feedback.

The example repo is available on GitHub. The Swagger.json generated from the example repo is available on LINK.

2024 © Emir Ribic - Some rights reserved; please attribute properly and link back. Code snippets are MIT Licensed

Powered by Hugo & Kiss.