loading...
Cover image for Setting Up Swagger Docs for Golang API

Setting Up Swagger Docs for Golang API

martinheinz profile image Martin Heinz Originally published at martinheinz.dev ・7 min read

Note: This was originally posted at martinheinz.dev

In the previous post - Building RESTful APIs in Golang - we created RESTful API in Golang. Now, that we have our project ready to be used, we should show our users how to do so, otherwise - if they can't test it and view its features - they won't even touch it.

Without meaningful documentation of our APIs as well as an ability to test its endpoints, users won't even bother trying to use it. Solution to that is writing documentation. However, writing it may take lots of time, which could otherwise used to develop more cool features for our applications... So, what do we do? - we generate Swagger docs!

Swagger Golang

Libraries

Let's start with libraries needed to create the Swagger docs. I said libraries, but really just need one - swag - which is Golang library that converts code annotations (comments) to Swagger Documentation 2.0. Aside from swag you will need a middleware/wrapper library for your web framework. In swag docs there are links to libraries for supported frameworks, which include both the simplest option of net/http which a lot of people like to use as well as GIN, which I use and which I will show here. Even though you might be using different web framework, the annotations are gonna be the same, so you can learn something here anyway.

It's also worth to mention, that there is alternative Golang Swagger library - go-swagger which seems to be more popular and also quite a bit more powerful. I, personally, however prefer to swaggo/swag because of its simplicity. If you need more control over what gets generated you might want switch to go-swagger.

Docstrings

Now, for the annotations/comments/docstring or whatever you want to call it. It's really just bunch of comments before specific API function, which is used to generate the Swagger docs.

Before we get to describing individual API endpoints, we need to first write general description for our whole project. This part of annotations lives in your main package, right before the main function:

Note: All the examples below come from my repository here, where you can find runnable application with the Swagger UI/Docs included.

import (
    ...
    swaggerFiles "github.com/swaggo/files"
    "github.com/swaggo/gin-swagger"

    _ "github.com/MartinHeinz/go-project-blueprint/cmd/blueprint/docs"
)

// @title Blueprint Swagger API
// @version 1.0
// @description Swagger API for Golang Project Blueprint.
// @termsOfService http://swagger.io/terms/

// @contact.name API Support
// @contact.email martin7.heinz@gmail.com

// @license.name MIT
// @license.url https://github.com/MartinHeinz/go-project-blueprint/blob/master/LICENSE

// @BasePath /api/v1
func main() {
    ...
    r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
    ...
}

Above you can see example of General API Info, which includes things like name, version, license, base URL etc. There are few more fields that you can include and they are listed here with some examples.

Apart from the annotations, we also need to import necessary libraries including blank import of our docs package that we have to generate (more on that later). One more thing we need to do, is to actually mount the Swagger UI at some endpoint, here we use "/swagger/*any.

This is the part of UI, which the annotations above would produce:

Now for the important part - annotations for API functions. These annotations precede each function that is wired in main to serve some endpoint, so when we serve endpoint like v1.GET("/users/:id", apis.GetUser), we need to annotate it like this:

// GetUser godoc
// @Summary Retrieves user based on given ID
// @Produce json
// @Param id path integer true "User ID"
// @Success 200 {object} models.User
// @Router /users/{id} [get]
func GetUser(c *gin.Context) {
    ...
}

Most of these are pretty self-explanatory and this is really minimal set of annotations that you should include. One thing I want to highlight though, is the models.User being returned on success - this is a model of database table that lives in models package. By referencing it like this, we cause it to appear in Swagger UI in the models section:

And this is a section that we get for our endpoint:

Generate!

Finally, it's time to generate the docs! All you need is one command - swag init, this command needs to be ran from directory where main is, so for the blueprint repository I made, it would be .../cmd/blueprint/. This command will create package called docs, which includes both JSON and YAML version of our docs.

Even though this package is generated, I prefer to store it in GitHub, as it is imported in the main package and therefore it's necessary for application to run. If you want to avoid pushing this generated code to GitHub, you could for example write a Makefile target, that would re-generate the Swagger docs on-the-fly before application is built and ran. If you however, decide to push it GitHub, you might want to run the docs through go fmt as it's not necessarily formatted "as it should be".

Authentication

At this point, we could just run the application, look at our nice new Swagger UI and call it a day. One thing that is missing though, is authentication for the API. If you leave Swagger UI unauthenticated, then anybody can hit any endpoint they want, which might be very undesirable, if - for example - your data could be damaged by users. Even worse, you might expose sensitive information from your database to the whole internet. I think those are enough reasons to setup some simple authentication for our API and therefore, also Swagger UI, so how do we do it?

First of all, we need to actually implement the authentication. Here, is case of GIN, we create a very simple authentication middleware, which we attach to router group:

func auth() gin.HandlerFunc {  // Middleware function
    return func(c *gin.Context) {
        authHeader := c.GetHeader("Authorization")  // Retrieve the header
        if len(authHeader) == 0 {  // Check if it's set
            httputil.NewError(c, http.StatusUnauthorized, errors.New("Authorization is required Header"))
            c.Abort()
        }
        if authHeader != config.Config.ApiKey {  // Check if the API Key is correct
            httputil.NewError(c, http.StatusUnauthorized, fmt.Errorf("this user isn't authorized to this operation: api_key=%s", authHeader))
            c.Abort()
        }
        c.Next()
    }
}

By attaching the middleware to specific group(s) we can control what is and what is not authenticated, which is important because we - for example - don't want Swagger UI itself to be authenticated.

Note: I've omitted some of the code, to make the examples readable and easy to follow. For full code, see the rest-api branch in repository here.

One more thing that we need to change in main module is annotations - more specifically, we need to add the securityDefinitions annotation:

// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
func main() {
    ...
}

This annotation - as you can already guess - adds API key authentication through Authorization header to the Swagger UI. Apart from API key authentication you could also choose to use basic authentication (securitydefinitions.basic) using username and password or some version of OAuth2 (securitydefinitions.oauth2), all options are shown in documentation here. I personally like to use API key as it is simple and the most convenient option in my opinion.

For Swagger to recognize, that some endpoint is authenticated, we also need to add security annotation to said API function:

// @Security ApiKeyAuth
func GetUser(c *gin.Context) {
    ...
}

This was the last step and now (after regenerating Swagger docs) we can finally run our application:

foo@bar:~$ docker run -p 1234:1234 \
               -e BLUEPRINT_DSN="postgres://<USER>:<PASSWORD>@<DOCKER_SERVICE/URL>:<PORT>/<DB>?sslmode=disable" \
               -e BLUEPRINT_API_KEY="api_key" \
               docker.pkg.github.com/martinheinz/go-project-blueprint/blueprint:latest

And you should see something like this in GIN logs:

[GIN-debug] GET    /swagger/*any             --> github.com/swaggo/gin-swagger.CustomWrapHandler.func1 (3 handlers)  # <- Swagger is ready!
[GIN-debug] GET    /api/v1/users/:id         --> github.com/MartinHeinz/go-project-blueprint/cmd/blueprint/apis.GetUser (4 handlers)

We can now open the Swagger UI at http://localhost:1234/swagger/index.html and test our documentation!

Note: If the command above doesn't make much sense to you, please consider also reading through previous post here.

So, just to recap - the security definition in main package gives us following modal:

Then, if we enter incorrect API key ("wrong_api_key") we get 401 response code:

And if we use correct API key we get back 200 with the data requested:

It's important to also mention, that sending authorization headers in plaintext like we have done here is not secure whatsoever and defeats the whole purpose of authenticating the API, so in real application you should definitely use HTTPS.

Conclusion

After reading this article, I hope you now know, how to go about setting up Swagger docs for your API and I also hope that you will actually write some docs for your next project as it's pretty simple and there's great value in good API documentation for both you (manual testing) and users of your application. You can checkout full code in my repository's rest-api branch here and if you have questions or improvements, feel free to reach out to me or create issue/pull request in the repository. πŸ™‚

Discussion

pic
Editor guide
Collapse
midacts profile image
John McCarthy

Great article. It was really helpful for me to get a better understanding around creating a rest api in golang and using swagger docs.

I was struggling to find the best path to create swagger docs and you explained the reasoning which I agree with.

I haven't gotten to the point of implementing authentication yet, but when I do, I will be back. Although, I hope I can use Active Directory for an authentication backend.