Quick Doc is an easy-to-use Go module which can be used to generate API documentations using OpenAPI tech stack [1]. Quick Doc follows OpenAPI 3.0.3 guidelines.
The purpose of developing this is to,
-
Use OpenAPI tech stack to create API documentation
-
Easy integration with existing APIs with minimal code change
-
Auto generate OpenAPI schema(s) from Go objects
-
Use with other frameworks and modules. Ex: gin, mux-router, http
-
Provide easy to use, OpenAPI web viewer
-
Add new endpoint quickly with fewer changes
Unlike lexical parsing methods, this method works at the runtime. There for there is a few of advantages and disadvantages can be addressed,
| Pros | Cons |
|---|---|
| Easy to use with existing auto-completion | There is an overhead at API start time. Can be reduced by serializing JSON spec string. |
| No boilerplate comments | No automated way to identify the endpoints which haven’t documented yet. |
| It can be easily integrated with existing APIs with a minimal code change. | |
| Very flexible. | |
| No need of additional script or pipeline |
Install,
go get -u https://github.com/pickme-lk/quick-doc@v1.0.0
Import qdoc and ui packages from quick-doc module,
import (
"https://github.com/pickme-lk/quick-doc/qdoc"
"https://github.com/pickme-lk/quick-doc/ui"
)
Create a new OpenAPI document,
doc := qdoc.NewDoc(qdoc.Config{
Title: "Quick Doc Demo",
Description: "Quick Doc demo API documentation example",
Version: "1.0.0",
Servers: qdoc.Servers(
"http://localhost:8080",
"http://dev.quickdoc.com",
),
SpecPath: "/doc/json",
UiConfig: qdoc.UiConfig{
Enabled: true,
Path: "/doc/ui",
DefaultTheme: ui.SWAGGER_UI,
ThemeByQuery: false,
LogoUrl: "<logoURL>",
},
})
Add an endpoint details,
doc.Post(&qdoc.Endpoint{
Path: "/doc/user",
Desc: "Create a new user",
ReqBody: qdoc.ReqJson(doc.Schema(ReqUserAdd{
Name: "Student 1",
Age: 16,
Project: &Project{
Name: "Volunteer Project",
Description: "This is a volunteer project",
},
})),
RespSet: qdoc.RespSet{
Success: qdoc.ResJson("User creation success", nil),
},
}).Tag("User").WithBearerAuth()
Compile the quick-doc object,
cd, err := doc.Compile()
if err != nil {
panic(err)
}
Get http.ServeMux handler,
fmt.Println("Server is running on port 8080")
fmt.Println("Swagger UI: http://localhost:8080/doc/ui")
err = http.ListenAndServe(":8080", s)
if err != nil {
return
}
Complete Example (main.go),
package main
import (
"fmt"
"https://github.com/pickme-lk/quick-doc/qdoc"
"https://github.com/pickme-lk/quick-doc/ui"
"net/http"
)
type Project struct {
Name string `json:"name"`
Description string `json:"description"`
Description2 string `json:"description2"`
}
type ReqUserAdd struct {
Name string `json:"name"`
Age int `json:"age"`
Project *Project `json:"project"`
}
func main() {
Doc()
}
func Doc() {
doc := qdoc.NewDoc(qdoc.Config{
Title: "Quick Doc Demo",
Description: "Quick Doc demo API documentation example",
Version: "1.0.0",
Servers: qdoc.Servers(
"http://localhost:8080",
"http://dev.quickdoc.com",
),
SpecPath: "/doc/json",
UiConfig: qdoc.UiConfig{
Enabled: true,
Path: "/doc/ui",
DefaultTheme: ui.SWAGGER_UI,
ThemeByQuery: false,
},
})
doc.Post(&qdoc.Endpoint{
Path: "/doc/user",
Desc: "Create a new user",
ReqBody: qdoc.ReqJson(doc.Schema(ReqUserAdd{
Name: "Student 1",
Age: 16,
Project: &Project{
Name: "Volunteer Project",
Description: "This is a volunteer project",
},
})),
RespSet: qdoc.RespSet{
Success: qdoc.ResJson("User creation success", nil),
},
}).Tag("User").WithBearerAuth()
cd, err := doc.Compile() // generate open api json target
if err != nil {
panic(err)
}
s := cd.ServeMux()
fmt.Println("Server is running on port 8080")
fmt.Println("Swagger UI: http://localhost:8080/doc/ui")
err = http.ListenAndServe(":8080", s)
if err != nil {
return
}
}
qdoc package provides a function called qdoc.NewDoc(...) to create a new document configuration object. qdoc.NewDoc function accepts a qdoc.Config{} object.
| Field | Type | Description |
|---|---|---|
| Title | string |
Open API documentation title. This will be used as both OpenAPI spec title and UI title. |
| Description | string |
(Optional) Open API documentation description. This support markdown. |
| Version | string |
(Optional) Version information for OpenAPI specification. Example: 1.0.0 |
| Servers | []string |
List of API host servers. There is a helper function to increase readability and constancy. Example: qdoc.Servers( |
| AuthConf | qdoc.AuthConf |
(Optional) Define authentication methods for API. There is a helper function to define this field. This field can be ignored, then automatically decide according to endpoint authentication details. Example: qdoc.NewAuthConf().WithBearer() |
| SpecPath | string |
(Optional) URL path to serve OpenAPI JSON. Default value is set to /doc/openapi.json |
| UiConfig | qdoc.UiConfig |
(Optional) See below for more details |
| Field | Type | Description |
|---|---|---|
| Enabled | boolean |
Enable or disable built-in OpenAPI web viewer. Default value is false. |
| Path | string |
(Optional) URL path to serve OpenAPI web viewer. Default value will be set to /doc/ui. Make sure that SpecPath and UiConfig.Path has same prefix string. |
| DefaultTheme | ui.Theme |
(Optional) ui.SWAGGER_UI or ui.RAPI_DOC, default value is ui.SWAGGER_UI |
| ThemeByQuery | boolean |
(Optional) When this is set to true. Web viewer accepts optional query parameter called theme=swagger-ui or theme=rapi-doc |
| LogoUrl | string |
CDN image URL to show in Swagger UI. |
Example of creating document configuration object,
doc := qdoc.NewDoc(qdoc.Config{
Title: "Quick Doc Example",
Description: "Quick Doc Example API documentation example",
Version: "1.0.0",
Servers: qdoc.Servers(
"http://localhost:8080",
"http://dev.quickdoc.com",
"http://quickdoc.com",
),
SpecPath: "/doc/json",
AuthConf: qdoc.NewAuthConf().WithBearer(),
UiConfig: qdoc.UiConfig{
Enabled: true,
Path: "/doc/ui",
DefaultTheme: ui.SWAGGER_UI,
ThemeByQuery: true,
},
})
Examples can be found at the end of this step.
Doc object provide Get, Post, Put, Delete methods which can be used to add endpoints to configuration object. Each of method accept a pointer to a qdoc.Endpoint object.
| Field | Type | Description |
|---|---|---|
| Path | string |
URL path of the endpoint Example: /api/user |
| Summary | string |
(Optional) Brief summary about endpoint Example: get current user details |
| Description | string |
(Optional) Descriptive details about endpoint. This field has Markdown support |
| ReqBody | qdoc.RequestBody |
(Optional) Request body schema and other details. Quick doc provides a couple of helper functions for creating these.qdoc.ReqJson - create JSON request.qdoc.ReqJson - create JSON request.qdoc.ReqJson - create JSON request.qdoc.ReqForm - create URL encoded form data request.qdoc.ReqBody - create custom request body with custom content types. All of these functions accept a pointer to a qdoc.SchemaConfig which provide details to generate OpenAPI schema. For more details about qdoc.SchemaConfig can be found below. Examples can be found below. |
| QueryParams | qdoc.Parameters |
(Optional) Define query parameters in the request. Quick doc provides a couple of helper functions for creating these.qdoc.QueryParams - create qdoc.Parametersqdoc.QueryParams - create qdoc.Parametersqdoc.OptionalParam - create optional parameterqdoc.RequiredParam - create required parameterBoth of these functions accepts two arguments, name: string - parameter name`sc: *qdoc.SchemaConfig - pointer to schema config (optional) Examples can be found below. |
| PathParams | qdoc.Parameters |
(Optional) Define path parameters in the request. Same helper functions specified in QueryParams applied here.qdoc.PathParams - create qdoc.Parameters |
| Headers | qdoc.Parameters |
(Optional) Define header parameters in the request. Same helper functions specified in QueryParams applied here.qdoc.Headers - create qdoc.Parameters |
| RespSet | qdoc.RespSet |
Define set of response for the endpoint. Quick doc provide helper functions,type RespSet struct {qdoc.ResJson - define a JSON response.Examples can be found below. |
Quick Doc provides a helper method inside document configuration object to create SchemaConfig objects.
Example can be found below,
doc := qdoc.NewDoc(...)
doc.Schema(object: interface{}) // returns SchemaConfig pointer
Quick Doc provides two helper function,
qdoc.OptionalParam(
name: string // parameter name
sc: *qdoc.SchemaConfig // schema config to define paramter schema
)
qdoc.RequiredParam(
name: string // parameter name
sc: *qdoc.SchemaConfig // schema config to define paramter schema
)
Quick Doc provides 3 helper function,
qdoc.ReqJson(
sc: *qdoc.SchemaConfig // schema config to define req schema
)
qdoc.ReqForm(
sc: *qdoc.SchemaConfig // schema config to define req schema
)
qdoc.ReqBody(
sc: *qdoc.SchemaConfig // schema config to define req schema
)(
[]string // content types
)
type RespSet struct {
Success *Response
BadReq *Response
UnAuth *Response
Forbidden *Response
NotFound *Response
ISE *Response
others map[HttpStatus]*Response
}
Quick Doc provides helper functions to create Response objects
qdoc.ResJson(
desc: string // breif description about response
sc: *SchemaConfig // schema config to define response schema
)
Types and structs used in below examples,
type Team struct {
Name string `json:"name"`
Description string `json:"description"`
}
type User struct {
Username string `json:"username"`
Password string `json:"password"`
Age int `json:"age"`
Team string `json:"team"`
}
-
Post request example
doc.Post(&qdoc.Endpoint{ Path: "/api/user", Desc: "Create a new user", ReqBody: qdoc.ReqJson(doc.Schema(User{ Username: "testuser1", Password: "123456", Age: 24, Team: "testteam1", })), RespSet: qdoc.RespSet{ Success: qdoc.ResJson("User creation success", nil), BadReq: qdoc.ResJson("Invalid user data", nil), ISE: qdoc.ResJson("Internal server error", nil), }, }).Tag("User") -
Get endpoint example with Path parameter
doc.Get(&qdoc.Endpoint{ Path: "/api/user/{userId}", Desc: "Get user by user id", PathParams: qdoc.PathParams( qdoc.RequiredParam("user id", doc.Schema(0)), // 0 is int type and example value will be 0 ), RespSet: qdoc.RespSet{ Success: qdoc.ResJson("User found", doc.Schema(User{})), // schema and example will be generated NotFound: qdoc.ResJson("User not found", nil), ISE: qdoc.ResJson("Internal server error", nil), }, }).Tag("User").WithBearerAuth() // Add bearer token authentication requirement -
Get request example with query parameter
doc.Get(&qdoc.Endpoint{ Path: "/api/user", Desc: "Get users", QueryParams: qdoc.QueryParams( qdoc.OptionalParam("username", doc.Schema("testuser1")), // example value will be "testuser1" qdoc.OptionalParam("age", doc.Schema(11)), // example value will be 11 qdoc.OptionalParam("team", doc.Schema("testteam1")), // example value will be "testteam1" ), RespSet: qdoc.RespSet{ Success: qdoc.ResJson("User found", doc.Schema(User{})), // schema and example will be generated NotFound: qdoc.ResJson("User not found", nil), ISE: qdoc.ResJson("Internal server error", nil), }, }).Tag("User").WithBearerAuth() // Add bearer token authentication requirement -
Post request example with required request header
doc.Post(&qdoc.Endpoint{ Path: "/api/team", Desc: "Create a new team", ReqBody: qdoc.ReqJson(doc.Schema(Team{ Name: "testteam1", Description: "test team 1", })), Headers: qdoc.Headers( qdoc.RequiredParam("origin", doc.Schema("mobile-app")), // example value will be "mobile-app" ), RespSet: qdoc.RespSet{ Success: qdoc.ResJson("Team creation success", nil), BadReq: qdoc.ResJson("Invalid team data", nil), ISE: qdoc.ResJson("Internal server error", nil), }, }).Tag("Team").WithBearerAuth() // Add bearer token authentication requirement -
Get request with complex schema
doc.Get(&qdoc.Endpoint{ Path: "/api/team/{teamId}", Desc: "Get team with users", PathParams: qdoc.PathParams( qdoc.RequiredParam("team id", doc.Schema(0)), // 0 is int type and example value will be 0 ), RespSet: qdoc.RespSet{ Success: qdoc.ResJson("Team found", doc.Schema(struct { Team Team `json:"team"` Users []User `json:"users"` }{ Team: Team{ Name: "testteam1", Description: "test team 1", }, Users: []User{ { Username: "testuser1", Password: "123456", Age: 24, Team: "testteam1", }, }, })), // schema and example will be generated NotFound: qdoc.ResJson("Team not found", nil), ISE: qdoc.ResJson("Internal server error", nil), }, }).Tag("Team").WithBearerAuth() // Add bearer token authentication requirement
Compiling
cd, err := doc.Compile() // returns *CompiledDoc object
if err != nil {
panic(err) // when compilation is failed
}
Serving
CompiledDoc object has cd.ServeMux method which returns a *http.ServeMux http request multiplexer. Which can be used to serve both web UI and JSON spec string.
// SpecPath = /doc/json
// UIPath = /doc/ui
s := cd.ServeMux()
// Using builtin http router
err = http.ListenAndServe(":8080", s)
// Using Gorilla mux router
r := mux.NewRouter()
r.PathPrefix("/doc/").Handler(OpenApiRouter())
fmt.Println("listening on :8080")
http.ListenAndServe(":8080", r)
// Using Gin framework
r := gin.Default()
r.Group("/doc/*w").GET("", gin.WrapH(s))
r.Run()
-
Create document configuration options including enable/disable switch,
// Example type DocConfig struct { Enabled bool Title string Desc string Version string Servers []string SpecPath string UiEnabled bool UiPath string } -
Create new file
doc.goinsideinternal/transport/httppackage. (or create a new package) -
Create
InitDocsfunctionfunc InitDocs(router) { // check configuration switch if !config.DocConf.Enabled { return } doc := qdoc.NewDoc(qdoc.Config{ Title: config.DocConf.Title, Description: config.DocConf.Desc, Version: config.DocConf.Version, Servers: qdoc.Servers(config.DocConf.Servers...), SpecPath: config.DocConf.SpecPath, // doc/json UiConfig: qdoc.UiConfig{ Enabled: config.DocConf.UiEnabled, Path: config.DocConf.UiPath, // doc/ui DefaultTheme: ui.SWAGGER_UI, ThemeByQuery: true, }, }) // add enpoints doc.Get(...) doc.Post(...) // compile doc cd, err := doc.Compile() // returns *CompiledDoc object if err != nil { log.Error("failed to compile Open API doc") } // serving s := cd.ServeMux() router.handle("/doc/*", s) } -
Call
InitDocswhile router initializationfunc InitRouter() { ... router := ... ... InitDocs(router) ... }
Quick Doc module consists of 3 packages,
-
schemapackage -
uipackage -
qdocpackage
Go reflect is a built-in package which expose runtime reflections. This reflection details can be used to extract information from Golang types such as field types, tags. https://pkg.go.dev/reflect
This schema package gets use of Go reflect package to generate schema from Golang objects. It builds a Property object according to given Go object.
Features
-
Configurable object exploration algorithm
-
Respect json tag to generate key name
-
Support pointer variables
-
Extracting information from nil values
-
Support Integer, Float, Boolean, String, Array, Slice, Map, Struct types.
-
Support additional tags
-
[ToDo] Support constraint validations
-
[ToDo] Support recursive types
Example
type UserAccount struct {
Name string `json:"name"`
Age int `json:"age,omitempty"`
Logs []LogEntry `json:"logs"`
}
type LogEntry struct {
Date string `json:"date"`
Msg bool `json:"msg"`
}
sb := schema.NewBuilderDefault()
prop, err := sb.GetSchema(UserAccount{
Name: "Test User",
Age: 22,
Logs: []LogEntry{
{
Date: "2022-01-21",
Message: "account created",
}
},
})
There are plenty of different OpenAPI viewers can be found. The most famous OpenAPI viewer is Swagger UI. It supports both OpenAPI 2, OpenAPI 3 specifications. Swagger UI
This ui package provides a couple of configurable and easy to use Open API web viewers.
Features
- Configurable
- Swagger UI
- Rapi Doc UI
This is the primary package provides by quick doc module. Its features can be split into 3 primary functions.
- Building API documentation config
- Compiling documentation config into OpenAPI specs
- Serve OpenAPI JSON and web UI over HTTP
Features
- Easy and simple configuration
- Builtin OpenAPI web viewer
- Generate OpenAPI schema from Go objects
- Supported Auth methods: Basic, Bearer Token
- Multiple response support with schema
- JSON, Form and Multipart form request body support
- Query, Path parameter support
- Header parameter support
- Endpoint tag support
- and more…