DEV Community

Nguyễn Phúc Vinh
Nguyễn Phúc Vinh

Posted on

Tonic - Swaggo alternative

Introduction

My team just switched my main programming language from JavaScript to Go last month and tried to find an API docs library for our new project..

But frameworks like Gin or Echo do not support it natively like Fastify does. We found Swaggo, a wonderful tool, but it's required many steps: writing godocs, then use CLI to gen specs, and finally host swagger UI from specs. We just need a library that directly reflects my structs & routes definitions, then creates a Swagger UI for me, straightforwards and fully automatic.

Seems like the Go style is design-first approach, it's hard to find a library that meets our needs. So I created a Go lib, named Tonic, and want to share with community.

Visit my repo: https://github.com/TickLabVN/tonic

For the first release, Tonic may not support all features of OpenAPI and has some limitations. Welcome all contributions to make Tonic better.

Main ideas

Using the native lib reflect, Tonic reads struct's metadata like JSON tag, data type... and generates an object schema for the struct. For example:

type ArticleDTO struct {
    ID      int     `json:"id"`
    Title   string  `json:"title" binding:"required,min=4,max=255"`
    Content     string  `json:"content" binding:"required,min=20"`
}

// Will be generated to
{
    "id: {
        "type": "integer"
    },
    "title": {
        "type: "string",
        "minLength": 4,
        "maxLength": 255
    },
    "content": {
        "type: "string",
        "minLength": 20
    }
}
Enter fullscreen mode Exit fullscreen mode

Route defintion in Tonic:

app := gin.Default()
rg := app.Group("/api")
tonic.CreateRoutes(rg.BasePath(), []tonic.Route{
    {
        Method: Tonic.Get,
        Url: "/ping",
        HandlerRegister: func(path) {
            rg.GET(path, Ping)
        },
        Schema: &tonic.RouteSchema{
            Response: map[int]interface{}{
                200: PingResponse{},
            }
        },
    }
})
Enter fullscreen mode Exit fullscreen mode

After all, Tonic reads all route definitions and JSON schemas generated from structs, constructs all of these pieces of information into a Swagger object at runtime, then uses an external library to host the Swagger UI.

Tonic is not tightly coupled with any frameworks, developers can easily integrate it with existing projects.

Full example with Gin

main.go's content:

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/TickLabVN/tonic"
    "net/http"
)

type PingResponse struct {
    Message string `json:"message"`
}

func Ping(c *gin.Context) {
    response := PingResponse{
        Message: "pong",
    }
    c.JSON(200, response)
}

func main() {
    r := gin.Default()

    tonic.Init(&tonic.Config{
        OpenAPIVersion: "3.0.0",
        Info: map[string]interface{}{
            "title":       "Go CRUD Example",
            "description": "A simple CRUD example using Go and PostgreSQL",
            "version":     "1.0.0",
        },
    })

    rg := r.Group("/api")
    {
        tonic.CreateRoutes(rg.BasePath(), []tonic.Route{
            {
                Method: tonic.Get,
                Url: "/ping",
                HandlerRegister: func(path string) {
                    rg.GET(path, Ping)
                },
                Schema: &tonic.RouteSchema{
                    Response: map[int]interface{}{
                        200: PingResponse{},
                    },
                },
            },
        })
    }

    // tonic.GetHandler() returns the net/http handler for serving the swagger documentation
    r.GET("/docs/*w", gin.WrapH(http.StripPrefix("/docs", tonic.GetHandler())))

    r.Run(":8080")
}
Enter fullscreen mode Exit fullscreen mode

Result:

Swagger example

Top comments (0)