Let’s Go, Everything you need to know about creating a RESTful Api in Go — Part III

Supun Muthutantrige
12 min readApr 21, 2019

--

Detailed overview on integrating swagger

PART III

What is Swagger

Have you ever come across a situation where you have implemented set of apis to be used by other components but find it difficult to explain. Things such as what’s the URI, what’s the request payload looks like, what are the HTTP status codes to be expected and so on. Other engineers might also asked you to send curl commands or Postman collections to get an idea about the Apis.

Swagger provides a better way to overcome some of the main challengers when it comes to develop APIs. It provides a set of tools to make the best use of APIs. One way to deal with the above mentioned limitation in APIs is to provide an API documentation where anyone can get the full picture of how to use it, or in best case scenario, the ability to interact with the API spec. Thus API consumers know what to expect and how to work with it.

Swagger provides multiple options when dealing with APIs. But what we want is to be able to provide an API documentation. That is to provide an API spec for our previously implemented go RESTful APIs.

What we have is a set of APIs written in go,

[app.go]package main
...
func main() {
r := mux.NewRouter()
r.HandleFunc("/accounts", createAccountHandler).Methods("POST")
r.HandleFunc("/accounts/{id}", getAccountHandler).Methods("GET")
r.HandleFunc("/accounts/{id}",deleteAccountHandler)
.Methods("DELETE)
log.Fatal(http.ListenAndServe(":8080", r))
}
...

We want it to be accessible to any consumer, by making it look something like this,

Steps to follow in order to create a swagger documentation for our go REST APIs is as follows,

Steps

  1. Add swagger annotations to our go rest api endpoints
  2. Generate swagger doc — swagger.json
  3. visualize swagger doc (swagger.json) using SwaggerUI

Prerequisites

We should have a tool to complete Steps 1 and 2, which is to generate a swagger.json files from a predefined set of annotations written to describe our api endpoints. Developers coming from Spring, Spring Boot background, should be familiar with annotations such as @ApiOperation, @ApiResponses and etc. Similarly, there should be a tool in golang which supports similar sort of api annotations, once executed, generates a swagger.json file which then can be visualized via SwaggerUI.

Go-Swagger

A golang implementation of Swagger 2.0, which provides several swagger specific features in go. It includes generating go servers and clients from swagger spce and what matters to us, is the ability to generate a swagger specification from go annotated code.

[Steps]go-swagger --------------> swagger.json --------------> SwaggerUI
(generate) (visualize)

install go swagger

There are many ways to install go-swagger (source), but herein used the following approach.

go get -u github.com/go-swagger/go-swagger/cmd/swagger

This will add the swagger dependency to bin folder.

go-swagger dependency

Check whether go-swagger works by running the following command

swagger generate spec -o ./swagger.json

Note: make sure that swagger executable could be accessible when executing the above statement.

Ex: if your current location is outsider bin folder,go
/bin
|-- swagger
/pkg
/src

execute the above as

$ bin/swagger generate spec -o ./swagger.json

Above will generate a swagger.json file, but since we didn’t add any go specific annotations to our code, it will only include something similar to,

[swagger.json]{
"swagger": "2.0",
"paths": {}
}
[File Path]
go
/bin
|-- swagger
/pkg
/src
|--swagger.json

With swagger.json file been created, we know that go-swagger works as intended. Now It’s time to add annotations to our go REST endpoints.

1. Add swagger annotations to rest api endpoints

From the previous article (PART III), we’ve completed our REST endpoints. The full source code is as follows,

[app.go]package mainimport (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
)
//Account Json request payload is as follows,
//{
// "id": "1",
// "first_name": "james",
// "last_name": "bolt",
// "user_name": "james1234"
//}
type Account struct {
ID string `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
UserName string `json:"user_name"`
}
var accountMap map[string]Accountfunc init() {
accountMap = make(map[string]Account)
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/accounts", createAccountHandler).Methods("POST")
r.HandleFunc("/accounts/{id}", getAccountHandler).Methods("GET")
r.HandleFunc("/accounts/{id}",deleteAccountHandler)
.Methods("DELETE)
log.Fatal(http.ListenAndServe(":8080", r))
}
func createAccountHandler(w http.ResponseWriter, r *http.Request) {
log.Print("Request received to create an Account")

var account Account
json.NewDecoder(r.Body).Decode(&account)
id := account.ID
accountMap[id] = account
log.Print("Successfully created the Account ", account)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(account)
}
func getAccountHandler(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
id := params["id"]
log.Print("Request received to get an account by account id: "
,id)
account, key := accountMap[id]
w.Header().Add("Content-Type", "application/json")
if key {
log.Print("Successfully retrieved the account ", account, " for
account id: ", id)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(account)
} else {
log.Print("Requested account is not found for account id: ",id)
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w)
}
}
func deleteAccountHandler(w http.ResponseWriter, r *http.Request) {
log.Print("Request received to delete an Account by account id")
//add your own flavor to this function :)
}

Will start adding go-swagger specific annotations to the above code, which will then be used to generate the swagger.json api spec.

If you want a detailed overview of go-swagger provided annotations, refer the guide (source). First will start adding annotations to get the API spec meta information.

Add go-swagger meta annotations to our go source file.

[app.go]// Package classification Account API.
//
// this is to show how to write RESTful APIs in golang.
// that is to provide a detailed overview of the language specs
//
// Terms Of Service:
//
// Schemes: http, https
// Host: localhost:8080
// Version: 1.0.0
// Contact: Supun Muthutantri<mydocs@example.com>
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Security:
// - api_key:
//
// SecurityDefinitions:
// api_key:
// type: apiKey
// name: KEY
// in: header
//
// swagger:meta
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
)
...

2. Generate swagger doc

Execute the following command to generate the swagger.json file.

$ bin/swagger generate spec -o ./swagger.json

The generated swagger.json file for the annotations we specified is as follows,

[swagger.json]consumes:
- application/json
produces:
- application/json
schemes:
- http
- https
swagger: '2.0'
info:
description: |-
the purpose of this application is to show how to write RESTful APIs in golang.
that is to provide a detailed overview of the language specs
title: Account API.
contact:
name: Supun Muthutantri
email: mydocs@example.com
version: 1.0.0
host: localhost:8080
paths: {}
securityDefinitions:
api_key:
type: apiKey
name: KEY
in: header
security:
- api_key: []

3. visualize swagger doc

Swagger.io has an online editor for us to visualize swagger specifications. You just have to provide a json or yaml file similar to what was generated in step 2.

Copy the above specification in swagger.json and paste it in the editor.

swagger online editor

You can use this editor to visualize the generated API spec and improve it until you get the final version.

4. Iterate steps 1, 2 and 3 till you get the full API spec

Will continue adding annotations to our go REST endpoints. Let’s add go-swagger annotations to the GET endpoint.

[app.go]...func main() {   ...    // swagger:operation GET /accounts/{id} accounts getAccount
// ---
// summary: Return an Account provided by the id.
// description: If the account is found, account will be returned
else Error Not Found (404) will be returned.
// parameters:
// - name: id
// in: path
// description: id of the account
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/accountRes"
// "400":
// "$ref": "#/responses/badReq"
// "404":
// "$ref": "#/responses/notFoundReq"
r.HandleFunc("/accounts/{id}", getAccountHandler).Methods("GET")
...}...

As you can see, we have added annotations above GET handler function which looks like comments. Whether the endpoint it GET, POST or DELETE the structure remains more or less similar.

Things to note

  • swagger:operation → Links a path to the method (source)
swagger:operation [method] [path pattern] [?tag1 tag2 tag3] [operation id]
  • parameters →URL parameters, in our case it’s the description of {id} in /accounts/{id}
  • responses → Description of endpoint responses, it mentions the status codes, error messages and response payloads.
  • $ref → Relates to responses, links to the annotated structs.

$ref should contain all the response structs annotated in a similar way. Here Response codes for 200, 400 and 404 should contain separate annotated structs for accountRes, badReq and notFoundReq.

[Preparing accountRes][Annotated Account struct]
// Account request model
type Account struct {
// Id of the account
ID string `json:"id"`
// First Name of the account holder
FirstName string `json:"first_name"`
// Last Name of the account holder
LastName string `json:"last_name"`
// User Name of the account holder
UserName string `json:"user_name"`
}
[Annotated accountRes response struct]
// Account response payload
// swagger:response accountRes
type swaggAccountRes struct {
// in:body
Body Account
}

As you can see, in order to define the swagger annotations for response codes and response payloads we have created a separate struct swaggAccountRes. Read more on swagger:response.

Complete annotations for our GET endpoint is as follows,

[app.go]...// Account request model
type Account struct {
// Id of the account
ID string `json:"id"`
// First Name of the account holder
FirstName string `json:"first_name"`
// Last Name of the account holder
LastName string `json:"last_name"`
// User Name of the account holder
UserName string `json:"user_name"`
}
// Account response payload
// swagger:response accountRes
type swaggAccountRes struct {
// in:body
Body Account
}
// Error Bad Request
// swagger:response badReq
type swaggReqBadRequest struct {
// in:body
Body struct {
// HTTP status code 400 - Bad Request
Code int `json:"code"`
}
}
// Error Not Found
// swagger:response notFoundReq
type swaggReqNotFound struct {
// in:body
Body struct {
// HTTP status code 404 - Not Found
Code int `json:"code"`
}
}
...func main() {...// swagger:operation GET /accounts/{id} accounts getAccount
// ---
// summary: Return an Account provided by the id.
// description: If the account is found, account will be returned
else Error Not Found (404) will be returned.
// parameters:
// - name: id
// in: path
// description: id of the account
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/accountRes"
// "400":
// "$ref": "#/responses/badReq"
// "404":
// "$ref": "#/responses/notFoundReq"
r.HandleFunc("/accounts/{id}", getAccountHandler).Methods("GET")
...}...

For ease of referencing I have bold the reference structs in the above code snippet. Similarly, you can add annotations to POST and DELETE endpoints as well.

Once completed the go endpoints with the annotations will look as follows,

[app.go]...// Account request model
type Account struct {
// Id of the account
ID string `json:"id"`
// First Name of the account holder
FirstName string `json:"first_name"`
// Last Name of the account holder
LastName string `json:"last_name"`
// User Name of the account holder
UserName string `json:"user_name"`
}
// Account response payload
// swagger:response accountRes
type swaggAccountRes struct {
// in:body
Body Account
}
// Success response
// swagger:response okResp
type swaggRespOk struct {
// in:body
Body struct {
// HTTP status code 200 - OK
Code int `json:"code"`
}
}
// Error Bad Request
// swagger:response badReq
type swaggReqBadRequest struct {
// in:body
Body struct {
// HTTP status code 400 - Bad Request
Code int `json:"code"`
}
}
// Error Not Found
// swagger:response notFoundReq
type swaggReqNotFound struct {
// in:body
Body struct {
// HTTP status code 404 - Not Found
Code int `json:"code"`
}
}
...func main() {
r := mux.NewRouter()
// swagger:operation POST /accounts/ accounts createAccount
// ---
// summary: Creates a new account.
// description: If account creation is success, account will be
returned with Created (201).
// parameters:
// - name: account
// description: account to add to the list of accounts
// in: body
// required: true
// schema:
// "$ref": "#/definitions/Account"
// responses:
// "200":
// "$ref": "#/responses/okResp"
// "400":
// "$ref": "#/responses/badReq"
r.HandleFunc("/accounts", createAccountHandler).Methods("POST")
// swagger:operation GET /accounts/{id} accounts getAccount
// ---
// summary: Return an Account provided by the id.
// description: If the account is found, account will be returned
else Error Not Found (404) will be returned.
// parameters:
// - name: id
// in: path
// description: id of the account
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/accountRes"
// "400":
// "$ref": "#/responses/badReq"
// "404":
// "$ref": "#/responses/notFoundReq"
r.HandleFunc("/accounts/{id}", getAccountHandler).Methods("GET")
// swagger:operation DELETE /accounts/{id} accounts deleteAccount
// ---
// summary: Deletes requested account by account id.
// description: Depending on the account id, HTTP Status Not
Found (404) or HTTP Status OK (200) may be returned.
// parameters:
// - name: id
// in: path
// description: account id
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/okResp"
// "400":
// "$ref": "#/responses/badReq"
// "404":
// "$ref": "#/responses/notFoundReq"
r.HandleFunc("/accounts/{id}",
deleteAccountHandler).Methods("DELETE")
}
...

Now execute swagger generation command.

$ bin/swagger generate spec -o ./swagger.json

Which will generate the following swagger.json file.

[swagger.json]consumes:
- application/json
produces:
- application/json
schemes:
- http
- https
swagger: '2.0'
info:
description: |-
the purpose of this application is to show how to write RESTful APIs in golang
that is to provide a detailed overview of the language specs
title: Account API.
contact:
name: Supun Muthutantri
email: mydocs@example.com
version: 1.0.0
host: localhost:8080
paths:
/accounts/:
post:
description: 'If account creation is success, account will be returned with Created (201).'
tags:
- accounts
summary: Creates a new account.
operationId: createAccount
parameters:
- description: account to add to the list of accounts
name: account
in: body
required: true
schema:
$ref: '#/definitions/Account'
responses:
'200':
$ref: '#/responses/okResp'
'400':
$ref: '#/responses/badReq'
'/accounts/{id}':
get:
description: 'If the account is found, account will be returned else Error Not Found (404) will be returned.'
tags:
- accounts
summary: Return an Account provided by the id.
operationId: getAccount
parameters:
- type: string
description: id of the account
name: id
in: path
required: true
responses:
'200':
$ref: '#/responses/accountRes'
'400':
$ref: '#/responses/badReq'
'404':
$ref: '#/responses/notFoundReq'
delete:
description: 'Depending on the account id, HTTP Status Not Found (404) or HTTP Status OK (200) may be returned.'
tags:
- accounts
summary: Deletes requested account by account id.
operationId: deleteAccount
parameters:
- type: string
description: account id
name: id
in: path
required: true
responses:
'200':
$ref: '#/responses/okResp'
'400':
$ref: '#/responses/badReq'
'404':
$ref: '#/responses/notFoundReq'
definitions:
Account:
description: Account request model
type: object
properties:
first_name:
description: First Name of the account holder
type: string
x-go-name: FirstName
id:
description: Id of the account
type: string
x-go-name: ID
last_name:
description: Last Name of the account holder
type: string
x-go-name: LastName
user_name:
description: User Name of the account holder
type: string
x-go-name: UserName
x-go-package: app
responses:
accountRes:
description: Account response payload
schema:
$ref: '#/definitions/Account'
badReq:
description: Error Bad Request
schema:
type: object
properties:
code:
description: HTTP status code 400 - Bad Request
type: integer
format: int64
x-go-name: Code
forbiddenReq:
description: Error Forbidden
schema:
type: object
properties:
code:
description: HTTP status code 403 - Forbidden
type: integer
format: int64
x-go-name: Code
notFoundReq:
description: Error Not Found
schema:
type: object
properties:
code:
description: HTTP status code 404 - Not Found
type: integer
format: int64
x-go-name: Code
okResp:
description: Success response
schema:
type: object
properties:
code:
description: HTTP status code 200 - OK
type: integer
format: int64
x-go-name: Code
securityDefinitions:
api_key:
type: apiKey
name: KEY
in: header
security:
- api_key: []

You can use the generated swagger.json file to visualize the API spec using Swagger editor.

Interact with SwaggerUI and execute api’s via swaggerUI

Up until now we have been able to generate the swagger.json and visualize using swagger editor. In order to be able to serve the swagger spec within our application, will use swaggerUI.

SwaggerUI

“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 OpenAPI (formerly known as Swagger) Specification, with the visual documentation making it easy for back end implementation and client side consumption” - (source)

With our swagger.json in place will use swaggerUI to make our API spec available via the same go server. Will see how we can setup swaggerUI inside our code base to serve generated swagger.json file.

  1. Download and integrate SwaggerUI to our code base
  • Go to swagger-ui github page (link), and clone the repo
git clone https://github.com/swagger-api/swagger-ui.git
  • Copy the dist folder inside swagger-ui directory and paste it inside our go source folder src directory and rename it to swaggerui
[go source folder]
go
/bin
/pkg
/src
|--swaggerui (dist dir renamed to swaggerui)
  • Copy previously generated swagger.json file in to swaggerui folder and change “swagger url” in index.html file inside swaggerui pointing to the swagger.json file.
[index.html] 
...
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "./swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
...

2. Change our code base to access swaggerui via URI

[app.go]...func main() {
...
fs := http.FileServer(http.Dir("./swaggerui"))
r.PathPrefix("/swaggerui/").Handler
(http.StripPrefix("/swaggerui/", fs))
log.Fatal(http.ListenAndServe(":8080", r))
}
...

3. Run go application which will start the internal go server

$ go run app.go

4. Navigate to the browser and view swaggerui.

http://localhost:8080/swaggerui/

You can trigger the endpoints via swaggerui and witness how it works.

So it is the end of another article. Next, will see how to integrate a database to our endpoints.

checkout my personal blog to view more — icodeforpizza

checkout my latest video content blogging series for golang — youtube

Prev Section — Part II

Next Section — Part IV

--

--