Tonic - Swaggo alternative
Tags: go, restapi, swagger, opensource
Published: 2024-02-17 03:14:10

## 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](https://github.com/gin-gonic/gin) or [Echo](https://github.com/labstack/echo) do not support it natively like Fastify does. We found [Swaggo](https://github.com/swaggo/swag), 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:

```go
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
    }
}
```

Route defintion in Tonic:

```go
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{},
            }
        },
    }
})
```

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: 
```go
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")
}
```

Result:

![Swagger example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q993ja7o6htmhgru4h4e.png)