Swagger with Go walkthrough
17 Jul '20 • 2 min read
Why Swagger?
When writing API docs, there are two approaches you can take:
- Write them in a markdown file, update that everytime something changes (see Monzo’s API docs).
- Auto-generate them directly from your code.
We’re going to take a look at one way of achieving option 2. Auto-generating docs enforces thorough documentation in the code itself, while making your API docs super maintainable by removing the risk of our docs getting out-of-sync with our backend.
Swagger is the most widely used open-source tool that let’s you do 2. After Swagger handles the auto-generation of API docs, you can pass that output to another tool to lay on some HTML and CSS to make it beautiful (tools include Swagger UI, Slate)
Choosing a library
There are two libraries available for using Swagger with Go:
Long story short, swag
is simpler and go-swagger
is more powerful and more widely used. You can read-up on some more discussion of what’s best here, here, and here.
We went for go-swagger
, as we’d like as much flexibility with the API docs as possible and value the increased engagement with the project.
Let’s generate some docs for our new doggies API…
We’re going to walk through making a super-simple API and generating some docs with Swagger 💪
Our project is going to look like this:
.
├── api
│ └── api.go
├── docs
│ ├── docs.go
│ └── dog.go
├── go.mod
├── go.sum
├── main
│ └── main.go
└── swagger.yaml
Doggies API: Setup the project
First, we’re going to create files needed for a super-simple Go server:
.
├── api
│ └── api.go
└── main
└── main.go
We’ll be using the external library gorilla/mux
to serve our API, so go ahead and install that with go get -u github.com/gorilla/mux
. Then make sure we’ve initialised our go modules with go mod init
.
New, we need an endpoint so we have something to generate.
Doggies API: Create an endpoint
Add the following to main.go
and api.go
respectively.
package main
import (
"log"
"net/http"
"github.com/DoggyAPI/api"
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
router.HandleFunc("/dog", api.CreateDog).Methods("POST")
log.Fatal(http.ListenAndServe(":5000", router))
}
package api
import (
"encoding/json"
"fmt"
"net/http"
)
// Dog contains the properties of a dog.
type Dog struct {
Name string `json:"name"`
Age int `json:"age"`
HasWaggyTail bool `json:"has_waggy_tail"`
}
// DogRequest contains the body of a Dog request.
type DogRequest struct {
Name string `json:"name"`
Age int `json:"age"`
HasWaggyTail bool `json:"has_waggy_tail"`
}
// DogResponse returns a Dog object.
type DogResponse struct {
Dog Dog `json:"dog"`
}
// CreateDog handles incoming dog creation requests
func CreateDog(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application-json")
var newDog Dog
json.NewDecoder(r.Body).Decode(&newDog)
fmt.Println("New dog!", newDog)
}
You should now be able to run go run main/main.go
to start your server without any errors.
Great! Now we’re ready to start generating our docs.
Doggies API: Swagger setup
First, let’s create a docs
package to store the comments and structs required by Swagger.
.
├── api
│ └── api.go
├── docs
│ └── docs.go
└── main
└── main.go
We’ll also have to import it in main.go
, so that the docs
package is built by Go (without that import, there’d be no entry point for our docs
package).
import (
"log"
"net/http"
"github.com/DoggyAPI/api"
_ "github.com/DoggyAPI/docs"
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
router.HandleFunc("/dog", api.CreateDog).Methods("POST")
log.Fatal(http.ListenAndServe(":5000", router))
}
Now let’s fill in our docs
package.
We’re going to add the following to docs.go
:
// Package classification Doggy.
//
// Documentation of our Doggy API.
//
// Schemes: http
// BasePath: /
// Version: 1.0.0
// Host: pawsome.com
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Security:
// - basic
//
// SecurityDefinitions:
// basic:
// type: basic
//
// swagger:meta
package docs
Notice how it’s all comments except for the package name on the last line. This is intentional. Swagger will interpret these comments as metadata for our API. You can read more about it in Swagger’s own docs.
The main thing we’ve defined here is the name of our service, after Package classification
, and then a description of our service right after that.
Now, we’ll document our dogs
endpoint by adding a new file in our docs
package, dogs.go
.
.
├── api
│ └── api.go
├── docs
│ └── docs.go
| └── dogs.go
└── main
└── main.go
Here’s how we’ll do it:
package docs
import "github.com/DoggyAPI/api"
// swagger:route POST /dog dog-tag idDog
// Dog creates a new dog.
// responses:
// 200: dogSuccessResponse
// swagger:parameters idDog
type dogParamsWrapper struct {
// Dog creates a new dog.
// in:body
Body api.DogRequest
}
// Successful dog created response.
// swagger:response dogSuccessResponse
type dogResponseWrapper struct {
// in:body
Body api.DogResponse
}
There’s a few things to note here:
- The part following
swagger:route
defines the properties of our endpoint. It’s aPOST
request at/dog
. It has adog-tag
, which is used to group similar endpoints together in our docs. And it has anidDog
, which corresponds to the id abovedogParamsWrapper
a few lines below. - We then have a description “Dog creates a new dog.”, and a list of
responses
, each with an id, in this case onlydogSuccessResponse
, which corresponds to the id abovedogResponseWrapper
. - The
// Dog creates a new dog.
text will appear as the description of our request body - In the two types below, we use
// in:body
to point to the structs in ourapi
package that make up the request and response parameters.
We’re set! Let’s generate those docs ✨
Generate our docs
First, we’ll have to download the swagger
command, which let’s us generate and play with our new docs:
go get -u github.com/go-swagger/go-swagger/cmd/swagger
Generate and serve our yaml file
Run the first command to generate the swagger.yaml
file that will be the source of truth for our docs and the second command serve our docs locally💥
swagger generate spec -o ./swagger.yaml --scan-models
swagger serve -F=swagger swagger.yaml
You’ll never need to touch the swagger.yaml
file. Instead, you can regenerate it by updating the comments in docs.go
and the comments around your API endpoints before running swagger generate
once more.
You should have something that looks like this:
Conclusion
We’re done! Enjoy your shiny new API docs 👋
Written by Naz