Como deixar o Swagger com tema dark mode usando Swaggo e Golang

Wiliam V. Joaquim - Nov 11 '23 - - Dev Community

Recentemente criei um port mostrando como deixar o Swagger com tema dark mode utilizando NestJS, agora vou mostrar como deixar o Swagger em dark mode utilizando o Swaggo com Go.

O que é o Swaggo?

O Swaggo é uma ferramenta que nos ajuda a documentar nossa API desenvolvida em GO, gerando a documentação no padrão da OpenAPI.

Não vamos focar em como utilizar o Swaggo, mas vou deixar aqui um excelente post no Dev.to que ensina como fazer isso.

Criando o projeto de exemplo

Vamos criar um exemplo mais alinhado com uma utilização no mundo real, para isso vamos utilizar o Go Chi, que é o roteador muito simples, mas que facilita muito na criação de rotas em Go.

Vamos iniciar o projeto rodando o comando:

  go mod init swagger-dark-mode
Enter fullscreen mode Exit fullscreen mode

Isso vai criar nosso arquivo go.mod, que vai servir para gerenciar nossos pacotes.

Organizando o projeto

Project structure

Vamos organizar seguindo um padrão muito utilizado pela comunidade do Go, você pode ver nesse repositório

  • cmd: Aqui é onde vamos deixar nosso arquivos que iniciam a nossa aplicação.
    • webserver: Aqui é onde vamos deixar o main.go que inicia nosso webserver.
  • internal: Nessa pasta onde deve ficar todo o código da nossa aplicação.
    • handler: Aqui vai ficar os arquivos responsáveis por receber nossas solicitações http, você pode conhecer também como controllers.
    • routes: Aqui vamos organizar nossas rotas, incluido a rota da nossa documentação.

main.go:

  package main

  import (
    "fmt"
    "net/http"
    "swagger-dark-mode/internal/handler/routes"

    "github.com/go-chi/chi/v5"
  )

  func main() {
    r := chi.NewRouter()
    routes.InitRoutes(r)

    fmt.Println("Server running on port 8080")
    http.ListenAndServe(":8080", r)
  }
Enter fullscreen mode Exit fullscreen mode

No arquivo main.go, iniciamos nosso router com go chi, iniciamos nossas rotas com routes.InitRoutes(), e damos start em nosso server que vai rodar na porta 8080

Você pode baixar os pacotes do go chi e swaggo com o comando:

  go get github.com/swaggo/http-swagger github.com/go-chi/chi/v5
Enter fullscreen mode Exit fullscreen mode

É necessário instalar o swag na sua máquina, veja como neste link

routes.go:

  package routes

  import (
    "swagger-dark-mode/internal/handler"

    _ "swagger-dark-mode/docs"

    "github.com/go-chi/chi/v5"
    httpSwagger "github.com/swaggo/http-swagger"
  )

  var (
    docsURL = "http://localhost:8080/docs/doc.json"
  )

  //    @title      Swagger Dark Mode
  //    @version    1.0
  func InitRoutes(r chi.Router) {
    r.Get("/docs/*", httpSwagger.Handler(httpSwagger.URL(docsURL)))

    r.Get("/user", handler.GetUser)
  }
Enter fullscreen mode Exit fullscreen mode

No arquivo routes.go é onde criamos nossas rotas, criamos uma rota do tipo GET /user que chama nosso handler GetUser e outra rota
GET para nossa docs /docs/* o /* após o path docs, indica que qualquer combinação de caracteres pode aparecer na posição correspondente.

o import "_ swagger-dark-mode/docs" vem da pasta gerada após iniciar o swag, usando o comando:

  swag init -g internal/handler/routes/routes.go
Enter fullscreen mode Exit fullscreen mode

É necessário rodar esse comando sempre que houver alterações na sua documentação, o swag vai criar um pasta chamada docs, você não precisa alterar nada nessa pasta, veja como fica agora a estrutura do projeto:

Project structure

Dentro do swagger.json fica o arquivo no padrão da OpenAPI.

o caminho internal/handler/routes/routes.go deve ser onde está as anotações gerais do swag, no nosso caso setamos apenas o @title e o @version, veja todas as anotações possíveis aqui.

user.go:

  package handler

  import (
    "encoding/json"
    "log/slog"
    "net/http"
  )

  type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
  }

  // Get fake user
  //    @Summary         Get Fake user
  //    @Description Get Fake user for example
  //    @Tags              user
  //    @Accept          json
  //    @Produce         json
  //    @Success         200    {object}    User
  //    @Failure         500
  //    @Router          /user [get]
  func GetUser(w http.ResponseWriter, r *http.Request) {
    user := User{
      ID:    1,
      Name:  "John Doe",
      Email: "jonh.doe@email.com",
    }

    userMarshal, err := json.Marshal(user)
    if err != nil {
      slog.Error("Error marshalling user", err)
      http.Error(w, err.Error(), http.StatusInternalServerError)
      return
    }

    w.Write(userMarshal)
  }
Enter fullscreen mode Exit fullscreen mode

Se desejar formatar suas anotações do swag basta rodar o comando:

  swag fmt
Enter fullscreen mode Exit fullscreen mode

No arquivo user.go é onde podemos validar nossa requisição, onde podemos pegar o body por exemplo e transformar em um struct go. Criamos um user fake, fizemos o enconding da a struct User para Json usando Marshal, caso não aconteça um erro duranto o Marshal, retornamos o json usando o w.writer.

Rodando o projeto

Tudo pronto para rodar o projeto, caso não tenha instalado os pacotes, rode o comando:

  go mod tidy
Enter fullscreen mode Exit fullscreen mode

Esse comando vai no arquivo go.mod e faz o download das dependências.

Vamos iniciar o projeto, rodando nosso main.go

  go run cmd/webserver/main.go
Enter fullscreen mode Exit fullscreen mode

Se tudo estiver correto, vamos ter no terminal a mensagem Server running on port 8080.

Acessando nossa rota via http://localhost:8080/user teremos esse resultado:

{
  "id": 1,
  "name": "John Doe",
  "email": "jonh.doe@email.com"
}
Enter fullscreen mode Exit fullscreen mode

Acessando nossa outra rota http://localhost:8080/docs/index.html, teremos nossa documentação com swagger:

Swaggo light
Meme my eyes

Criando nosso CSS e JS

Bom, finalmente vamos ao intuito do post, deixar o tema do swagger em dark mode, infelizmente não existe nada nativo ou que seja simples quanto deixar o swagger em dark mode utilizando o NestJS. (Pelo menos até a data de pulbicação desse post).

Vamos precisar injetar nosso css customizado no swaggo, para isso você pode utilizar o css desse gist, por´m também não conseguimos injetar o css diretamente, mas conseguimos injetar um JavaScript, com isso também se torna possível manipular a DOM e consequentemente injetar css.

Vamos criar uma pasta dentro da pasta docs, chamado custom, onde vamos colocar nossas customizações.

Você pode criar fora da pasta docs, já que é uma pasta gerada dinamicamente pelo swaggo, pode ser substituida e acabar perdendo sua customização, mas para este exemplo vamos deixar dentro da pasta docs mesmo.

Dentro do custom vamos criar 2 arquivos, custom_css.go e custom_layout.go.

custom_css.go:

  package custom

  var customCSS = `css do gist`
Enter fullscreen mode Exit fullscreen mode

No arquivo custom_css.go, apenas retornamos o css que deixe no gist em string.

custom_layout.go:

  package custom

  import "fmt"

  var CustomLayoutJS = fmt.Sprintf(`
      // dark mode
      const style = document.createElement('style');
      style.innerHTML = %s;
      document.head.appendChild(style);
    `, "`"+customCSS+"`")
Enter fullscreen mode Exit fullscreen mode

No arquivo custom_layout.go criamos noss JavaScript para ser injetado em nosso swagger, criamos uma tag style e adicionamos ao DOM, convertamos em string utilizando o Sprintf do pacote fmt, veja um post meu sobre o pacote fmt aqui.

""+customCSS+"", isso envolve o arquivo JS em aspas duplas.

Aplicando o Dark mode

Vamos finalmente aplicar nosso dark mode, para isso vamos alterar no arquivo routes.go:

  func InitRoutes(r chi.Router) {
    r.Get("/docs/*", httpSwagger.Handler(httpSwagger.URL(docsURL),
      httpSwagger.AfterScript(custom.CustomJS),
      httpSwagger.DocExpansion("none"),
      httpSwagger.UIConfig(map[string]string{
      "defaultModelsExpandDepth": `"-1"`,
      }),
    ))

    r.Get("/user", handler.GetUser)
  }
Enter fullscreen mode Exit fullscreen mode

httpSwagger.AfterScript(custom.CustomJS): Isso injeta nosso JS no swagger depois da página ser carregada.
httpSwagger.DocExpansion("none"): Isso faz com que cada o endpoint abre expandido ou não (gosto pessoal), mas ajuda quando sua documentação possui muitos endpoints, no exemplo o -1 faz com que por padrão não fique expandido as rotas.
"defaultModelsExpandDepth": "-1": Isso faz com que os models sejam ocultos, caso precise deixar visível basta remover.

Existe mais opções possíveis nas docs do swagger.

Agora rodando novamente nosso projeto, já teremos o swagger em dark mode:
Swaggo dar mode
Meme liked

Personalizando ainda mais

Agora com o poder da manipulação da DOM e com paciência, podemos modificar o layout da forma que desejarmos, vamos por exemplo altera a logo e favicon e o title.

Vamos modificar nosso custom_layout.go

  var CustomJS = fmt.Sprintf(`
    // set custom title
      document.title = 'Swagger Dark Mode With Go';

      // set custom favicon
      const link = document.createElement('link');
      link.rel = 'icon';
      link.type = 'image/x-icon';
      link.href = 'data:image/png;base64,%s';
      document.head.appendChild(link);

      // set custom logo
      const image = document.querySelector('.link img');
      const base64URL = 'data:image/png;base64,%s';
      image.src = base64URL;

      // dark mode
      const style = document.createElement('style');
      style.innerHTML = %s;
      document.head.appendChild(style);
    `, CustomLogo, CustomLogo, "`"+customCSS+"`")
Enter fullscreen mode Exit fullscreen mode

Adicionei um title, favicon e logo, usando um base64 para as imagens. Salvamos esse base64 em um arquivo chamado images.go dentro da pasta custom.

  package custom

  var (
    CustomLogo = `base64 aqui`
  )
Enter fullscreen mode Exit fullscreen mode

Apenas como exemplo, o favicon e a logo foram utilizados a mesma imagem em base64, mas você poderia separar, veja como ficou:
Swaggo dar mode

Considerações finais

Como podemos ver, não é tão complicado personalizar, apesar da personalização ser um pouco trabalhosa e parecer não ser a mais adequeada, ainda conseguimos deixar o tema do swagger com uma aparência mais agradável para quem for consumir nossa api.

Link do repositório

repositório do projeto

link do projeto no meu blog

. . . . . . . . . . . . . . . . . . . . . .