Swaggo with Fiber in Golang

Swaggo with Fiber in Golang

Swagger API Documentation with Swaggo in Golang

In the last blog post, we talked about how we can create endpoints with the Fiber library. In this blog post, we will look at how we can integrate the Swagger into our Fiber endpoints with the Swaggo library.

Note: You can get the full source code from here.

If you prefer to watch the video version of this blog post, here it is.

If you don't know what Swagger is, it is a UI that allows you to visualize and interact with the API’s resources. It is probably the most popular API documentation tool in the software world. Here is a demo.

Swaggo is a library where you can generate swagger documentation with annotations. It can work with many web frameworks like Fiber and Gin.

To start, let's first download the Swaggo library.

go get -u github.com/swaggo/swag/cmd/swag

Now, we can run the swag init command in the Go project root folder, which contains main.go file.

swag init

Now you can see that there is a generated folder called docs, and there are files under it: docs.go, swagger.json, and swagger.yml. These are generated files with our annotations.

Now, we can download the Fiber Swaggo library.

go get -u github.com/gofiber/swagger

In order to make your Swagger doc work, you need to import Fiber Swagger and docs folder in main.go.

import (
    "github.com/gofiber/swagger"
    _ "yt-swagger-go/docs" // yt-swagger-go is my project name
)

Looks like we are ready. First, let's put our API name in the documentation by adding annotations to our main function in main.go and add our swagger endpoint to see in the browser.

//    @title            Order Api
//    @version        1.0
//    @description    This is an Order Api just for young people
//    @termsOfService    http://swagger.io/terms/
func main() {
    app := fiber.New()
    app.Get("/swagger/*", swagger.HandlerDefault)
    // code stuff
    app.Listen(":3000")
}

And now we can generate our swagger doc with swag init and swag fmt. swag fmt Checks spaces, etc. It is an optional command, and I just like to use it :)

Now, let's run our project and go to localhost:3000/swagger/index.html endpoint.

And we can see our main doc headers in Swagger! Great!

Now, let's start to document our endpoints. We have one GET and one POST endpoint. We can start with the GET endpoint.


Document GET Endpoint

Our endpoint is like this

func GetOrderByCode(app *fiber.App) fiber.Router {
    return app.Get("/orders/code/:orderCode", func(ctx *fiber.Ctx) error {
        fmt.Printf("Your correlationId is %v", ctx.Locals("correlationId"))

        return ctx.SendString("This is your order Code: " + ctx.Params("orderCode"))
    })
}

This endpoint gets orderCode from the path, controls correlationId which we have a middleware that controls if it is null or not, and returns orderCode within a string. The annotations can be like this.

// GetOrderByCode Getting Order by Code
//
//    @Summary        Getting Order by Code
//    @Description    Getting Order by Code in detail
//    @Tags            Orders
//    @Accept            json
//    @Produce        json
//    @Param            x-correlationid    header        string    true    "code of Order"
//    @Param            orderCode        path        string    true    "code of Order"
//    @Success        200                {string}    string
//    @Router            /orders/code/{orderCode} [get]
func GetOrderByCode(app *fiber.App) fiber.Router {
    return app.Get("/orders/code/:orderCode", func(ctx *fiber.Ctx) error {
        fmt.Printf("Your correlationId is %v", ctx.Locals("correlationId"))

        return ctx.SendString("This is your order Code: " + ctx.Params("orderCode"))
    })
}

Summary: Summary :)

Description: Detailed explanation of what this endpoint does.

Tags: How to group in swagger endpoints. I will talk about this later as well.

Accept: Accept request type

Product: Return request type

Param: This annotation gets more than one parameter. It is like this: param name, param type, data type, isMandatory, description(optional).

Success: Successful return status code and type

Router: URL and endpoint type (get, post, delete, etc.)

This is it for our first endpoint. Let's generate our swagger with swag init, and see the result in the browser.

Great, we can see our endpoint! Let's go to our POST endpoint.


Document POST Endpoint

First, let's see what the endpoint looks like.

func CreateOrder(app *fiber.App, customValidator *validation.CustomValidator) fiber.Router {
    return app.Post("/orders", func(ctx *fiber.Ctx) error {
        var request model.CreateOrderRequest
        err := ctx.BodyParser(&request)
        if err != nil {
            return err
        }

        if errs := customValidator.Validate(customValidator.Validator, request); len(errs) > 0 && errs[0].HasError {
            errorMessages := make([]string, 0)

            for _, err2 := range errs {
                errorMessages = append(errorMessages, fmt.Sprintf("%s field has failed. Validation is: %s", err2.Field, err2.Tag))
            }

            return ctx.Status(fiber.StatusBadRequest).JSON(strings.Join(errorMessages, " and that "))
        }

        return ctx.Status(fiber.StatusCreated).JSON("Order created successfully!")
    })
}

What this endpoint does? It is a POST endpoint, it gets a request from the body called CreateOrderRequest , parse it, validate the request, and if validation fails, returns BadRequest. If everything is good, return Created with a string.

Annotations would be like this.

// CreateOrder Creating Order
//
//    @Summary        Creating Order
//    @Description    Creating Order with given request
//    @Tags            Orders
//    @Accept            json
//    @Produce        json
//    @Param            x-correlationid    header        string                        true    "code of Order"
//    @Param            request            body        model.CreateOrderRequest    true    "Request of Creating Order Object"
//    @Success        200                {string}    string
//    @Failure        400                {string}    string    "Bad Request"
//    @Router            /orders [post]
func CreateOrder(app *fiber.App, customValidator *validation.CustomValidator) fiber.Router {
    // I am not showing here for keep the code snippet short.
}

Our Tag is still Orders, so it could be grouped with our GET endpoint. We have little new details in the annotations; let's look at them.

Param body: Now we are getting the request from the body and it is a custom struct, which we can write like package.StructName.

Failure: Fail return status code and type. (You didn't see that coming, right? :D)

Not let's run swag init and see our documentation.

We have two order endpoints under the same group, thanks to Tags annotation. Let's open our POST endpoint.

We can see header, our CreateOrderRequest object, and possible responses. Cool!


Document Request Object

Sometimes, we can have complex request objects and want to explain further by property. We can do this with Swaggo. Let's try this on our CreateOrderRequest object.

// CreateOrderRequest
// @Description Request about creating Order
type CreateOrderRequest struct {
    // shipment no of Order
    ShipmentNumber string `json:"shipmentNumber" validate:"required"`
    // country code like: tr, us
    CountryCode string `json:"countryCode" validate:"required,len=2"`
    // age to make sure you are young
    Age int `json:"age" validate:"required,oldAge"`
}

And here's the result of this. First swag init , then run the project again.

There are a lot of annotations in Swaggo, and you can enrich the documentation of your APIs as much as you want.

Thanks for reading.

May the force be with you!