DEV Community

Cover image for Como deixar o Swagger com tema dark mode usando Swaggo e Golang
Wiliam V. Joaquim
Wiliam V. Joaquim

Posted on

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

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

Top comments (1)

Collapse
 
anderson_matians profile image
Anderson Matias

Nossa, o tanto que sofri pra deixar isso em modo dark e não consegui hahah