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
Isso vai criar nosso arquivo go.mod
, que vai servir para gerenciar nossos pacotes.
Organizando o projeto
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.
-
webserver: Aqui é onde vamos deixar o
-
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)
}
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
É 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)
}
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
É 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:
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)
}
Se desejar formatar suas anotações do swag basta rodar o comando:
swag fmt
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
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
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"
}
Acessando nossa outra rota http://localhost:8080/docs/index.html
, teremos nossa documentação com swagger:
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`
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+"`")
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)
}
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:
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+"`")
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`
)
Apenas como exemplo, o favicon e a logo foram utilizados a mesma imagem em base64, mas você poderia separar, veja como ficou:
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