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:
- Adding annotations in code
- Generating Swagger specs (
swagger.json
andswagger.yaml
) - 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:
- query (indicates a query param)
- path (indicates a path param)
- header (indicates a header param)
- body
- 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
- Build a REST API in Golang with MySQL, GORM and Gorilla Mux
- Consuming REST APIs in Go - HTTP GET, PUT, POST and DELETE
- Build a REST API in Golang