Swagger UI setup for Go REST API using Swaggo


golang swagger rest api

In a previous post - Go REST API, we saw how to build a simple REST service in Golang. This post is intended as a follow-up, and explains how to generate swagger documentation and setup Swagger UI for our APIs using Swaggo.


Why Swagger?

Once we have our super-cool API ready, the next step is to share it with consumers and make it easy for them to understand and use the API. Building an API is only half the job 😄 The other half is enabling clients to use, explore and test the API without too much hassle. Without a clear documentation of APIs, consumers are going to find it hard to do all of the above, which leads to a serious loss in developer productivity.

Once setup, Swagger UI provides a convenient way for consumers to explore the API and play around with it. Also, APIs evolve over time and the documentation should reflect the changes accordingly (The number of bugs that arise due to improper(or non-existent) communication of changes to APIs is just too damn high!). With Swagger , updating/maintaining API documentation is a breeze - the developer just has to add/tweak annotations in the code , and the changes are incorporated when the API doc is generated next.


Wait - What are my options?

Swaggo and go-swagger are two of the most popular frameworks available for generating Swagger docs and UI (Looking at the number of stars on Github, go -swagger appears to be more popular). However, I found Swaggo to be simple and hassle-free and can be a good starting point for documenting APIs in Go. I will write another post on doing the same with go-swagger and try to provide some comparisons between both.

In our previous post on Go APIs, we used Go’s built-in net/http library in combination with Gorilla Mux for routing. We will retain the same for this post, and build upon it by adding swagger-specific libraries and annotations.


Swaggo setup

Our first task is to install the libraries we are dependent on. Run the following commands from the commandline:

go get -u github.com/swaggo/swag/cmd/swag
go get -u github.com/swaggo/http-swagger
go get -u github.com/alecthomas/template

The first two commands install swag and http-swagger respectively:

swag - This library converts Go annotations to Swagger 2.0 docs (swagger.json/swagger.yaml), which is later used by http-swagger to serve the Swagger UI.

http-swagger - This library helps to serve the Swagger UI using the docs generated by swag

The third command is to install template, a fork of Go’s text/template package. This dependency is required in the docs.go file generated by swag, and we’ll see an error while running the application without it.


Generate Swagger documentation

Let us divide this whole process of API documentation into 3 steps:

  1. Adding annotations in code
  2. Generating Swagger specs (swagger.json and swagger.yaml)
  3. Serving the Swagger UI using the specs generated in the previous step

1. Adding annotations in code

General API info

We start by adding a general description for the entire project by annotating our main method. These annotations are nothing but comments before the method definition, as you can see below.

// @title Orders API
// @version 1.0
// @description This is a sample serice for managing orders
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.email soberkoder@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /
func main() {
	router := mux.NewRouter()
	...    
	log.Fatal(http.ListenAndServe(":8080", router))

}

I feel these annotations are self-explanatory. For more information, the exhaustive list of all possible annotations is available on the swag github page. The annotation we need to pay attention to are the @host and @BasePath - Once the swagger UI is app and we try out sample API calls from the UI, this path will be used for API invocation.

API Operation annotations

Now that we have added project-level documentation, let’s add documentation to each individual API. For reference, I am only showing this for the getOrders API below.

// GetOrders godoc
// @Summary Get details of all orders
// @Description Get details of all orders
// @Tags orders
// @Accept  json
// @Produce  json
// @Success 200 {array} Order
// @Router /orders [get]
func getOrders(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(orders)
}

Again, a lot of these annotations are self-explanatory. The @Success annotation specifies how a successful response from the API looks like - 200 is the response code, {array} specifies that the response is an array of type Order.

This is a simple GET API, and doesn’t have any request body. The below is a POST method that has a request body.

// CreateOrder godoc
// @Summary Create a new order
// @Description Create a new order with the input paylod
// @Tags orders
// @Accept  json
// @Produce  json
// @Param order body Order true "Create order"
// @Success 200 {object} Order
// @Router /orders [post]
func createOrder(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	var order Order
	json.NewDecoder(r.Body).Decode(&order)
	prevOrderID++
	order.OrderID = strconv.Itoa(prevOrderID)
	orders = append(orders, order)
	json.NewEncoder(w).Encode(order)
}

The request body is described by the @Param annotation, which has the following syntax:

 @Param [param_name] [param_type] [data_type] [required/mandatory] [description]

The param_type can be one of the following values:

  1. query (indicates a query param)
  2. path (indicates a path param)
  3. header (indicates a header param)
  4. body
  5. formData

Example values for model attributes

In addition to documenting the request/response for each API, we can also provide example values for fields in the request. The API developer could provide valid/sensible samples for fields, so that clients can try out API from swagger UI, these sample values are used for the request payload. For example, we have provided example values using the example tag for the Order and Item model.

// Order represents the model for an order
type Order struct {
	OrderID      string    `json:"orderId" example:"1"`
	CustomerName string    `json:"customerName" example:"Leo Messi"`
	OrderedAt    time.Time `json:"orderedAt" example:"2019-11-09T21:21:46+00:00"`
	Items        []Item    `json:"items"`
}

// Item represents the model for an item in the order
type Item struct {
	ItemID      string `json:"itemId" example:"A1B2C3"`
	Description string `json:"description" example:"A random description"`
	Quantity    int    `json:"quantity" example:"1"`
}

The request payload generated by Swagger for the CreateOrder API looks something like the following:

{
  "customerName": "Leo Messi",
  "items": [
    {
      "description": "A random description",
      "itemId": "A1B2C3",
      "quantity": 1
    }
  ],
  "orderId": "1",
  "orderedAt": "2019-11-09T21:21:46+00:00"
}

2. Generate swagger.json

Once we are done annotating our main method and all the APIs, we shall generate the swagger docs with the swag init command, as below:

# In your project dir (~/GOPATH/src/swaggo-orders-api normally)
swag init -g orders.go

By default, the init command looks for the general API annotations in the file named main.go. If we have named our files differently, we can pass the file path(which in our case is orders.go) with the -g option.
We should see a similar output, and you can navigate to the docs directory and view the swagger.json file, if you are curious.

019/12/02 19:19:43 Generate swagger docs....
2019/12/02 19:19:43 Generate general API Info, search dir:./
2019/12/02 19:19:43 create docs.go at  docs/docs.go
2019/12/02 19:19:43 create swagger.json at  docs/swagger.json
2019/12/02 19:19:43 create swagger.yaml at  docs/swagger.yaml

3. Swagger UI

This step is pretty straightforward (very much like the previous steps 😄). All we are doing here is importing the httpSwagger library, and the swagger docs we generated in Step 2. If you are wondering about the _ in the imports, it’s just a way of importing packages for side-effects. (Wait, that’s not very clear either!) It means that our code is not explicitly calling any methods from the package, but it’s possible that the package may perform actions like registering handlers, for example.

import (
	"encoding/json"
	"log"
	"net/http"
	"strconv"
	"time"

	_ "swaggo-orders-api/docs" // docs is generated by Swag CLI, you have to import it.

	httpSwagger "github.com/swaggo/http-swagger"

	"github.com/gorilla/mux"
)

In addition to specifying the routes for all our APIs, we’ll have to define a route in our main method for serving the Swagger UI using the PathPrefix method.

// @title Orders API
// @version 1.0
// @description This is a sample service for managing orders
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.email soberkoder@gmail.com
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /
func main() {
	router := mux.NewRouter()
	// Create
	router.HandleFunc("/orders", createOrder).Methods("POST")
	// Read
	router.HandleFunc("/orders/{orderId}", getOrder).Methods("GET")
	// Read-all
	router.HandleFunc("/orders", getOrders).Methods("GET")
	// Update
	router.HandleFunc("/orders/{orderId}", updateOrder).Methods("PUT")
	// Delete
	router.HandleFunc("/orders/{orderId}", deleteOrder).Methods("DELETE")

	// Swagger
	router.PathPrefix("/swagger").Handler(httpSwagger.WrapHandler)
	log.Fatal(http.ListenAndServe(":8080", router))

}

The above code snippet shows our main method after adding the Swag annotations, and a route for swagger UI.

Finally, once we are done with all the APIs, and it’s time take them for a spin. To run the app, navigate to your project directory, and run the following commands:

go run orders.go

You can see your work coming to life by loading the swagger UI at http://localhost:8080/swagger/index.html

If everything goes well, we should be seeing a UI like below:

You can also view the swagger json at the following location:

http://localhost:8080/swagger/doc.json

Swagger also provides an option to visualize the swagger docs using the Swagger editor online. You can just copy the contents of doc.json or doc.yaml and pasting it on the editor on the left half of the page shows the swagger UI on the right half.

In addition to viewing the API documentation, we can see an API in action by trying it out directly from the Swagger UI. There should be a Try it out button at the top right corner for each API, clicking this should display a request with values generated based on the example values we provided earlier (We can edit it if required). Clicking on the execute button invokes the API and displays the response details (payload + headers).


Setting up a makefile

Now, what happens when we update our APIs (Add a new one, modify the request/response of existing ones, etc)? Does the documentation get updated automatically? Unfortunately, no 😞 We’ll have to run the swag init command to regenerate the docs to reflect the updated API. Since this is something we’ll find ourselves doing often, wouldn’t it be nice if this is automated? To automate this, we can setup a makefile and configure the swag init command to be executed everytime we run the application. In the below snippet, we create a make target named run, which generates the swagger docs before running the application.

run:
    swag init -g orders.go
    go run orders.go

When the run target is executed:

swaggo-orders-api$ make run
swag init -g orders.go
2020/06/20 08:12:24 Generate swagger docs....
2020/06/20 08:12:24 Generate general API Info, search dir:./
2020/06/20 08:12:24 Generating main.Order
2020/06/20 08:12:24 Generating main.Item
2020/06/20 08:12:24 create docs.go at docs/docs.go
2020/06/20 08:12:24 create swagger.json at docs/swagger.json
2020/06/20 08:12:24 create swagger.yaml at docs/swagger.yaml
go run orders.go

Conclusion

I hope you now know how to setup Swagger UI for Go APIs, and hope you do this for your APIs going forward. I again want to emphasize the importance of having a clean and updated documentation (If there is one thing you wanna take away from this post, let this be it 😄) ) You can find the entire code on Github - Please feel free to reach out if there are any questions or suggestions and I’d love to hear your thoughts in the comments, as always.


See Also