DEV Community

Cover image for Construindo uma API organizadinha em Golang usando Fiber
Alexandre Fernandes dos Santos
Alexandre Fernandes dos Santos

Posted on Edited on

Construindo uma API organizadinha em Golang usando Fiber

Depois de passar um tempo explorando Go e enfrentando alguns desafios com seu sistema de pacotes (aqui faço um mea culpa, pois é um sistema muito interessante quando bem utilizado), comecei a refletir sobre a melhor forma de organizar a API que estava desenvolvendo como estudo.

Consultei vários exemplos, inclusive do repositório do Fiber, e cheguei a uma estrutura que considero legível e fácil de expandir com novas funcionalidades.

Observação: Não afirmo que esta seja a melhor forma de organizar uma API em Go, mas foi a que atendeu às minhas necessidades e acredito que possa ser útil em muitos casos onde uma API genérica é necessária.

O que essa API faz?

Link do repositório

Esta API foi desenvolvida para armazenar informações sobre livros e possui apenas três endpoints:

  • GET: Retorna informações sobre um livro.
  • PUT: Adiciona ou altera informações sobre um livro.
  • DELETE: Exclui as informações de um livro.

Implementei apenas o essencial, utilizando o framework Fiber, que tem uma abordagem semelhante ao Express.js, algo que gostei bastante, combinado com as vantagens do Go, como menor consumo de memória e alta velocidade. Os dados são armazenados em um Redis, que pode ser inicializado usando um docker-compose.

Estrutura

| api_simples
├── docker-compose.yml
├── docs
│   └── estrutura.png
├── go.mod
├── go.sum
├── main.go
├── pkg
│   ├── configurations
│   │   └── database.go
│   ├── entities
│   │   └── book.go
│   ├── handlers
│   │   └── book_handler.go
│   ├── repositories
│   │   ├── book_repository.go
│   │   └── commons.go
│   └── routes
│       ├── book_router.go
│       └── routes.go
└── README.md
Enter fullscreen mode Exit fullscreen mode

Acredito que não seja suficiente apenas listar o conteúdo de cada pasta, mas sim explicar o contexto e a razão por trás dessa organização. Não seguirei a ordem acima, pois acho mais claro explicar de forma não linear. Vamos lá:

go.mod

module github.com/XandeCoding/codigos-de-artigos/golang/api_simples

go 1.19

require (
    github.com/go-redis/redis/v9 v9.0.0-beta.2
    github.com/gofiber/fiber/v2 v2.36.0
Enter fullscreen mode Exit fullscreen mode

Esse arquivo resolve várias questões relacionadas ao workspace do Go, permitindo criar um repositório em qualquer local sem problemas de acesso a pacotes externos ou locais.

Para criá-lo, executei o comando go mod init com o caminho do projeto no GitHub como argumento (github.com/XandeCoding/codigos-de-artigos/golang/api_simples). Embora não seja estritamente necessário usar o caminho completo do GitHub — poderia ser apenas o nome do projeto, como api_simples —, optei por incluí-lo por se tratar de um projeto público. Isso facilita a referência a arquivos específicos por mim ou por outras pessoas.

pkg e main.go

A pasta pkg contém o código principal da API, onde todas as funcionalidades são implementadas. O arquivo main.go, por sua vez, é responsável apenas pela inicialização da aplicação, sem implementar lógica alguma.

main.go:

package main

import (
    "github.com/XandeCoding/codigos-de-artigos/golang/api_simples/pkg/routes"
    "github.com/gofiber/fiber/v2"
)

func main() {
    app := fiber.New()
    routes.AddRoutes(app)

    app.Listen(":3000")
}
Enter fullscreen mode Exit fullscreen mode

pkg/configurations

Aqui estão os arquivos de configuração. No caso, database.go configura o acesso ao banco de dados. Se houvesse outras configurações, como configurações personalizadas do Fiber ou variáveis de ambiente, elas também ficariam aqui.

Exemplo de configuração de conexão com o Redis em database.go:

package configurations

import "github.com/go-redis/redis/v9"

func CreateClient() *redis.Client {
    redisDatabase := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
        Password: "",
        DB: 0,
    })

    return redisDatabase
}
Enter fullscreen mode Exit fullscreen mode

pkg/entities

As entidades são estruturas que podem ser utilizadas em várias partes da aplicação, como na análise de dados recebidos nos endpoints e nas operações de banco de dados. Centralizá-las em um local comum é vantajoso, embora em estruturas baseadas em funcionalidades específicas possa fazer mais sentido separá-las por escopos.

pkg/repositories

Este pacote contém as funções que interagem diretamente com o banco de dados Redis. Por exemplo, recebem a entidade book e realizam operações de inserção, atualização e exclusão. Se houvesse outra entidade, como library, suas funções relacionadas ficariam em um arquivo separado.

Trecho de book_repository.go:

type Repository struct {
    database *redis.Client
}

...

func (rdb Repository) GetBook(name string) string {
    ctx, cancel := getContext()
    defer cancel()
    book, _ := rdb.database.Get(ctx, name).Result()

    return book
}
Enter fullscreen mode Exit fullscreen mode

pkg/routes

As rotas foram organizadas em arquivos separados para melhor legibilidade e manutenção. O arquivo routes.go inicializa todas as rotas, enquanto book_router.go define as rotas específicas para o recurso book.

Trecho de routes.go:

func AddRoutes(app *fiber.App) *fiber.App {
    bookRouter(app)

    return app
}
Enter fullscreen mode Exit fullscreen mode

Em book_router.go, as rotas, métodos e handlers são definidos. Uma vantagem dessa estrutura é a possibilidade de criar instâncias reutilizáveis, como a conexão com o Redis, que pode ser compartilhada entre os endpoints.

Trecho de book_router.go:

func bookRouter(app *fiber.App) *fiber.App {
    bookRepository := repositories.NewRepository()

    app.Get("/book/:name", handlers.GetBookHandler(bookRepository))
    app.Put("/book", handlers.SetBookHandler(bookRepository))
    app.Delete("/book/:name", handlers.DeleteBookHandler(bookRepository))

    return app
}
Enter fullscreen mode Exit fullscreen mode

pkg/handlers

Os handlers são as funções chamadas pelos endpoints. Por exemplo, o endpoint PUT /book chama a função SetBookHandler, localizada em book_handler.go, que retorna a função a ser executada quando o recurso é acessado.

Código da função SetBookHandler:

func SetBookHandler(bookRepository *repositories.Repository) fiber.Handler {
    return func(c *fiber.Ctx) error {
        book := new(entities.Book)
        err := c.BodyParser(book)

        if err != nil {
            message := map[string]string{"message": "Invalid entry data"}
            return c.Status(400).JSON(message)
        }

        book_name_normalized := strings.ReplaceAll(
            strings.ToLower(book.Name), " ", "_",
        )
        key := "book:" + book_name_normalized
        bookRepository.SetBook(key, book)
        return c.Send(c.Body())
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusão

Espero ter ajudado a esclarecer alguns dos desafios iniciais ao desenvolver uma API, especialmente em uma linguagem com a qual não temos muita familiaridade. Essa estrutura foi eficaz para mim, mas comentários e feedbacks são sempre bem-vindos para continuarmos melhorando. Até a próxima! 👋

Top comments (0)