Introduction
Unlike FastAPI, Gin does not have OpenAPI integration built in. With FastAPI when you add a route, documentation is already generated. With Gin, this is not the case. But recently I integrated Swagger UI in one of my Go backends and I wanted to document that process.
Prerequisites
- An already existing Gin server
You might consider Building a Book Store API in Golang With Gin if you are starting from scratch. I'll be using the same book store and extending over it to integrate Swagger UI.
How Swagger works with Gin
The way Swagger works with other Go backend is not different they all have the same mechanism. But how does it really work and what do we have to really do?
Swagger uses the Go comment system which is very well integrated with documentation already as we know. We write comments in a pre-defined way, details of which we will see further ahead in the post. But mostly it is divided into 2 parts. The server itself and the routes.
Swagger has a CLI binary which when runs converts these comment documents into OpenAPI compliant documentation. The resultant file also includes OpenAPI server specs in JSON and YAML format.
Lastly, we route the generated content via a handler in our backend.
How do we do that? Let's see.
Setup Swagger CLI
The prime repository you should keep under your pillow is https://github.com/swaggo/swag. Both in terms of CLI and documentation.
We'll install a CLI application called swag
. Here's how:
go get -u github.com/swaggo/swag/cmd/swag
# 1.16 or newer
go install github.com/swaggo/swag/cmd/swag@latest
The first command will download the dependencies to integrate in the server application.
The second one is where we install the CLI.
Now you should be able to run the swag command:
$ swag -h
NAME:
swag - Automatically generate RESTful API documentation with Swagger 2.0 for Go.
USAGE:
swag [global options] command [command options] [arguments...]
VERSION:
v1.8.1
COMMANDS:
init, i Create docs.go
fmt, f format swag comments
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--help, -h show help (default: false)
--version, -v print the version (default: false)
Documenting the Gin Server
When you look at Swagger UI documentation for any OpenAPI enabled website, you'd see something like this:
You might be familiar with the low half part of the UI. This is what Swagger docs are for i.e. to document the routes.
But before we deal with the lower 50% part I want you to notice in the above picture is the top 50% part. Right from the Swagger Petstore header to Find out more about the Swagger anchor link.
This part shows the metadata about the API server itself. In this section, we are going to build that part. You can find the code to start with here. We'll be continuing on that code.
First of all, we need to go get the packages we need to work with:
go get -u github.com/swaggo/files
go get -u github.com/swaggo/gin-swagger
Now after we have done that, let's look at our main.go file. This is the main.go
at the moment:
package main
import "github.com/santosh/gingo/routes"
func main() {
router := routes.SetupRouter()
router.Run(":8080")
}
Add a route for Swagger Docs
We add a new router in the main function:
router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
The modified file would look like this:
-import "github.com/santosh/gingo/routes"
+import (
+ "github.com/santosh/gingo/routes"
+ swaggerFiles "github.com/swaggo/files"
+ ginSwagger "github.com/swaggo/gin-swagger"
+)
func main() {
router := routes.SetupRouter()
+ router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
+
router.Run(":8080")
}
swaggerFiles.Handler
here is the handler which has all the assets embedded in it. Assets like the HTML, CSS, and javascript files are shown on the documentation end.
ginSwagger.WrapHandler
is a wrapper around http.Handler
, but for Gin.
This should be enough for testing. Let's see how the docs render at <server address>/docs/index.html
.
The good news is API documentation site is up. The bad news is it does not look like a normal API documentation site. What did we miss?
Generate swagger docs every time you modify doc string
Swagger documentation is stored in Go's own docstring. We have a special syntax that we follow. More on that later. But first, let us learn how to generate docs.
$ swag init
2022/05/25 23:59:16 Generate swagger docs....
2022/05/25 23:59:16 Generate general API Info, search dir:./
2022/05/25 23:59:16 create docs.go at docs/docs.go
2022/05/25 23:59:16 create swagger.json at docs/swagger.json
2022/05/25 23:59:16 create swagger.yaml at docs/swagger.yaml
We run swag init
every time we update docs for our API. This generates 3 files inside a sub directory called docs/
.
$ tree docs
docs
├── docs.go
├── swagger.json
└── swagger.yaml
0 directories, 3 files
swagger.json
and swagger.yaml
are the actual specification which you can upload to services like AWS API Gateway and similar services. docs.go
is a glue code that we need to import into our server.
Let us now import the documentation to our main.go.
package main
import (
+ _ "github.com/santosh/gingo/docs"
"github.com/santosh/gingo/routes"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
+// @title Gingo Bookstore API
func main() {
router := routes.SetupRouter()
As you can see we have imported docs
the module by giving the full path of the module. We also have to prepend this import using _
because it is not explicitly used in main.go file.
You might also have noticed // @title Gingo Bookstore API
line. Please note its position as this is important. It is just above the main()
func. Also @title
is not random here. It is one of the keywords which is documented under General API Info. We'll see more of these as we go, but for now, let's regenerate our docs and review the API docs.
Looks like we are getting somewhere. :D
Add General API Info to Gin API Server
We saw @title
annotation in the last section. It is used to set the title for the API server. But it is not the only annotation available. There is a wide array of annotation in Swagger. You can find them in use in real life here.
I'm going to use some of them to construct metadata on my doc site.
// @title Gin Book Service
// @version 1.0
// @description A book management service API in Go using Gin framework.
// @termsOfService https://tos.santoshk.dev
// @contact.name Santosh Kumar
// @contact.url https://twitter.com/sntshk
// @contact.email sntshkmr60@gmail.com
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
- Run
swag init
. - Re-run the server.
- Check for update:
That's some metadata in there.
You might also have noticed that the API spec inside docs/ directory has changed. For example, here is the swagger.yaml
file from the docs/
dir.
{
"swagger": "2.0",
"info": {
- "title": "Gingo Bookstore API",
- "contact": {}
+ "description": "A book management service API in Go using Gin framework.",
+ "title": "Gin Book Service",
+ "termsOfService": "https://tos.santoshk.dev",
+ "contact": {
+ "name": "Santosh Kumar",
+ "url": "https://twitter.com/sntshk",
+ "email": "sntshkmr60@gmail.com"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+ },
+ "version": "1.0"
},
+ "host": "localhost:8080",
+ "basePath": "/api/v1",
"paths": {}
}
Now it's time to add docs for API endpoints.
Add API Operation Info to API Endpoints
Just like General API Info for the main server, there also is API Operation for individual routes/endpoints handlers.
They are more diverse than what I'm going to use here. The ones I'm going to use are just a subset of them. If you want to check out some real examples, you can find them here on each of the route handlers.
Here is modified handlers/books.go
:
"github.com/santosh/gingo/models"
)
-// GetBooks responds with the list of all books as JSON.
+// GetBooks godoc
+// @Summary Get books array
+// @Description Responds with the list of all books as JSON.
+// @Tags books
+// @Produce json
+// @Success 200 {array} models.Book
+// @Router /books [get]
func GetBooks(c *gin.Context) {
c.JSON(http.StatusOK, db.Books)
}
-// PostBook takes a book JSON and store in DB.
+// PostBook godoc
+// @Summary Store a new book
+// @Description Takes a book JSON and store in DB. Return saved JSON.
+// @Tags books
+// @Produce json
+// @Param book body models.Book true "Book JSON"
+// @Success 200 {object} models.Book
+// @Router /books [post]
func PostBook(c *gin.Context) {
var newBook models.Book
@@ -28,7 +41,14 @@ func PostBook(c *gin.Context) {
c.JSON(http.StatusCreated, newBook)
}
-// GetBookByISBN locates the book whose ISBN value matches the isbn
+// GetBookByISBN godoc
+// @Summary Get single book by isbn
+// @Description Returns the book whose ISBN value matches the isbn.
+// @Tags books
+// @Produce json
+// @Param isbn path string true "search book by isbn"
+// @Success 200 {object} models.Book
+// @Router /books/{isbn} [get]
func GetBookByISBN(c *gin.Context) {
isbn := c.Param("isbn")
- Run
swag init
. - Re-run the server.
- Check for updates:
And here is the updated swagger.yaml
:
basePath: /api/v1
+definitions:
+ models.Book:
+ properties:
+ author:
+ type: string
+ isbn:
+ type: string
+ title:
+ type: string
+ type: object
host: localhost:8080
info:
contact:
@@ -12,5 +22,58 @@ info:
termsOfService: https://tos.santoshk.dev
title: Gin Book Service
version: "1.0"
-paths: {}
+paths:
+ /books:
+ get:
+ description: Responds with the list of all books as JSON.
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.Book'
+ type: array
+ summary: Get books array
+ tags:
+ - books
+ post:
+ description: Takes a book JSON and store in DB. Return saved JSON.
+ parameters:
+ - description: Book JSON
+ in: body
+ name: book
+ required: true
+ schema:
+ $ref: '#/definitions/models.Book'
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.Book'
+ summary: Store a new book
+ tags:
+ - books
+ /books/{isbn}:
+ get:
+ description: Returns the book whose ISBN value matches the isbn.
+ parameters:
+ - description: search book by isbn
+ in: path
+ name: isbn
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.Book'
+ summary: Get single book by isbn
+ tags:
+ - books
swagger: "2.0"
Notice the new definitions
and paths
section added. It is for models and the endpoints respectively.
Bonus: Router Group for /api/v1
It is always a good idea to prepend your routes with api/v1
. v1
bit has a logic behind it. It is for the time when you have to introduce a breaking change which is not backward compatible. Maybe the input or the output has changes that might break millions of dependent client.
In these situations, you increment the version to something like api/v2
and let the older API server serve old from the old handler.
Right now all of the routes in gingo server start with /book
. We are going to change that.
Here is the modified routes/routes.go
.
func SetupRouter() *gin.Engine {
router := gin.Default()
- router.GET("/books", handlers.GetBooks)
- router.GET("/books/:isbn", handlers.GetBookByISBN)
- // router.DELETE("/books/:isbn", handlers.DeleteBookByISBN)
- // router.PUT("/books/:isbn", handlers.UpdateBookByISBN)
- router.POST("/books", handlers.PostBook)
+
+ v1 := router.Group("/api/v1")
+ {
+ v1.GET("/books", handlers.GetBooks)
+ v1.GET("/books/:isbn", handlers.GetBookByISBN)
+ // router.DELETE("/books/:isbn", handlers.DeleteBookByISBN)
+ // router.PUT("/books/:isbn", handlers.UpdateBookByISBN)
+ v1.POST("/books", handlers.PostBook)
+ }
return router
}
Here we use router.Group
to create a group with a path of /api/v1
. We then move all the route definitions into the group and surround it with braces. That is how we create a path route in Gin.
Conclusion
API documentation is an essential part of API documentation. Instead of documenting the endpoints anywhere else, we can document the routes right in the code. That way we only have 1 single source of truth. No need to maintain code and documentation separately. In turn, we get always up-to-date documentation.
Every backend server has some sort of support for Swagger UI. I have covered the basics for Gin, but if you use any other framework, I encourage you to look for your own framework.
Top comments (2)
How do you create a new user here using swagger (in post)
Creating user was out of scope of this post. But I'm pretty sure you'll find many resources on dev.to itself or elsewhere.