Neste tutorial, vamos criar uma API em GO para uma aplicação de TO-DO, utilizando Swagger para documentação e MySQL como banco de dados. Vamos seguir a arquitetura hexagonal e orientação a objetos para manter o código organizado e fácil de manter.
Passo 1: Configuração do ambiente
Certifique-se de ter o GO instalado em seu sistema. Você também precisará do MySQL instalado e configurado, você pode usar o post "Desenvolvendo uma API TO-DO com Nest.js, Swagger, MySQL e Docker" para usar o mesmo docker do MySQL e estrutura de tabela.
Passo 2: Crie o banco de dados e a tabela
Caso não esteja usando o mesmo docker para o MySQL conforme mencionado no passo anterior, vamos criar o banco de dados e a tabela necessária para nossa aplicação de TO-DO. Execute o seguinte script SQL:
CREATE DATABASE IF NOT EXISTS todo_db;
USE todo_db;
CREATE TABLE IF NOT EXISTS tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT
);
Passo 3: Crie a estrutura do projeto
Crie a estrutura do projeto da seguinte forma:
go-todo-api/
├── docs/
├── src/
│ ├── handlers/
│ │ └── todo_handler.go
│ ├── models/
│ │ └── todo.go
│ ├── repositories/
│ │ └── todo_repository.go
│ ├── router/
│ │ └── router.go
│ └── config/
│ ├── db.go
│ └── swagger.go
├── main.mod
└── go.mod
Gere o arquivo go.mod executando o comando:
go init go-todo-api
Passo 4: Instale as dependências
Vamos precisar do pacote gorilla/mux para lidar com as rotas da API, swaggo/http-swagger para a documentação com Swagger e go-sql-driver/mysql para o acesso ao MySQL. Instale-os usando o seguinte comando:
go get -u github.com/gorilla/mux
go get -u github.com/swaggo/http-swagger/v2
go get -u github.com/go-sql-driver/mysql
Passo 5: Implemente a lógica da aplicação
Vamos implementar a lógica da aplicação seguindo a arquitetura hexagonal. Aqui está um exemplo básico de como seria a estrutura do código:
models/todo.go
package models
type Task struct {
ID int `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
}
repositories/todo_repository.go
package repositories
import (
"database/sql"
"log"
"go-todo-api/src/models"
)
type TodoRepository struct {
db *sql.DB
}
func NewTodoRepository(db *sql.DB) TodoRepository {
return TodoRepository{
db: db,
}
}
func (r *TodoRepository) Create(task *models.Task) error {
query := "INSERT INTO tasks (title, description) VALUES (?, ?)"
_, err := r.db.Exec(query, task.Title, task.Description)
if err != nil {
log.Printf("Erro ao inserir tarefa no banco de dados: %v", err)
return err
}
return nil
}
func (r *TodoRepository) Update(id int, task *models.Task) error {
query := "UPDATE tasks SET title = ?, description = ? WHERE id = ?"
_, err := r.db.Exec(query, task.Title, task.Description, id)
if err != nil {
log.Printf("Erro ao atualizar tarefa no banco de dados: %v", err)
return err
}
return nil
}
func (r *TodoRepository) Delete(id int) error {
query := "DELETE FROM tasks WHERE id = ?"
_, err := r.db.Exec(query, id)
if err != nil {
log.Printf("Erro ao excluir tarefa no banco de dados: %v", err)
return err
}
return nil
}
func (r *TodoRepository) GetByID(id int) (*models.Task, error) {
query := "SELECT id, title, description FROM tasks WHERE id = ?"
row := r.db.QueryRow(query, id)
var task models.Task
err := row.Scan(&task.ID, &task.Title, &task.Description)
if err != nil {
log.Printf("Erro ao obter tarefa do banco de dados: %v", err)
return nil, err
}
return &task, nil
}
func (r *TodoRepository) GetAll() ([]*models.Task, error) {
query := "SELECT id, title, description FROM tasks"
rows, err := r.db.Query(query)
if err != nil {
log.Printf("Erro ao obter tarefas do banco de dados: %v", err)
return nil, err
}
defer rows.Close()
var tasks []*models.Task
for rows.Next() {
var task models.Task
err := rows.Scan(&task.ID, &task.Title, &task.Description)
if err != nil {
log.Printf("Erro ao ler linha de tarefa do banco de dados: %v", err)
return nil, err
}
tasks = append(tasks, &task)
}
return tasks, nil
}
handlers/todo_handler.go
package handlers
import (
"encoding/json"
"net/http"
"strconv"
"github.com/gorilla/mux"
"go-todo-api/src/models"
"go-todo-api/src/repositories"
)
type TodoHandler struct {
Repository repositories.TodoRepository
}
// @Summary Create a task
// @Description create a task
// @Tags tasks
// @Accept json
// @Produce json
// @Param task body models.Task true "Task"
// @Success 201 {object} models.Task
// @Failure 400 {object} string
// @Failure 500 {object} string
// @Router /tasks [post]
func (h *TodoHandler) CreateTask(w http.ResponseWriter, r *http.Request) {
var task models.Task
err := json.NewDecoder(r.Body).Decode(&task)
if err != nil {
http.Error(w, "Erro ao decodificar JSON", http.StatusBadRequest)
return
}
err = h.Repository.Create(&task)
if err != nil {
http.Error(w, "Erro ao criar tarefa", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(task)
}
// @Summary Update a task
// @Description update a task
// @Tags tasks
// @Accept json
// @Produce json
// @Param id path int true "Task ID"
// @Param task body models.Task true "Task"
// @Success 200 {object} models.Task
// @Failure 400 {object} string
// @Failure 500 {object} string
// @Router /tasks/{id} [put]
func (h *TodoHandler) UpdateTask(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "ID inválido", http.StatusBadRequest)
return
}
var task models.Task
err = json.NewDecoder(r.Body).Decode(&task)
if err != nil {
http.Error(w, "Erro ao decodificar JSON", http.StatusBadRequest)
return
}
err = h.Repository.Update(id, &task)
if err != nil {
http.Error(w, "Erro ao atualizar tarefa", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(task)
}
// @Summary Delete a task
// @Description delete a task
// @Tags tasks
// @Accept json
// @Produce json
// @Param id path int true "Task ID"
// @Success 204
// @Failure 400 {object} string
// @Failure 500 {object} string
// @Router /tasks/{id} [delete]
func (h *TodoHandler) DeleteTask(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "ID inválido", http.StatusBadRequest)
return
}
err = h.Repository.Delete(id)
if err != nil {
http.Error(w, "Erro ao excluir tarefa", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}
// @Summary Show a task
// @Description get task by ID
// @Tags tasks
// @Accept json
// @Produce json
// @Param id path int true "Task ID"
// @Success 200 {object} models.Task
// @Failure 404 {object} string
// @Router /tasks/{id} [get]
func (h *TodoHandler) GetTaskByID(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "ID inválido", http.StatusBadRequest)
return
}
task, err := h.Repository.GetByID(id)
if err != nil {
http.Error(w, "Tarefa não encontrada", http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(task)
}
// @Summary Get Task List
// @Description get all tasks
// @Tags tasks
// @Accept json
// @Produce json
// @Success 200 {object} []models.Task
// @Failure 500 {object} string
// @Router /tasks [get]
func (h *TodoHandler) GetAllTasks(w http.ResponseWriter, r *http.Request) {
tasks, err := h.Repository.GetAll()
if err != nil {
http.Error(w, "Erro ao obter tarefas", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(tasks)
}
router/router.go
package router
import (
"github.com/gorilla/mux"
"go-todo-api/src/handlers"
)
func NewRouter(handler *handlers.TodoHandler) *mux.Router {
r := mux.NewRouter()
s := r.PathPrefix("/todo/v1").Subrouter()
s.HandleFunc("/tasks", handler.CreateTask).Methods("POST")
s.HandleFunc("/tasks/{id}", handler.UpdateTask).Methods("PUT")
s.HandleFunc("/tasks/{id}", handler.DeleteTask).Methods("DELETE")
s.HandleFunc("/tasks/{id}", handler.GetTaskByID).Methods("GET")
s.HandleFunc("/tasks", handler.GetAllTasks).Methods("GET")
return s
}
A implementação da API está pronta, agora vamos as configurações para podermos gerar o Swagger, conexão com o banco de dados e execução da API
Passo 6: Configuração da conexão com o banco de dados
config/db.go
package config
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
var DB *sql.DB
func InitDB() {
dataSourceName := "seu_user:sua_senha@tcp(localhost:3306)/todo_db"
var err error
DB, err = sql.Open("mysql", dataSourceName)
if err != nil {
log.Fatalf("Erro ao conectar ao banco de dados: %v", err)
}
err = DB.Ping()
if err != nil {
log.Fatalf("Erro ao pingar o banco de dados: %v", err)
}
fmt.Println("Conexão com o banco de dados estabelecida com sucesso")
}
func CloseDB() {
err := DB.Close()
if err != nil {
log.Fatalf("Erro ao fechar conexão com o banco de dados: %v", err)
}
fmt.Println("Conexão com o banco de dados fechada com sucesso")
}
Passo 7: Configuração do Swagger
config/swagger.go
package config
import (
"net/http"
httpSwagger "github.com/swaggo/http-swagger/v2"
_ "go-todo-api/docs"
)
func SetupSwagger() http.Handler {
return httpSwagger.Handler(
httpSwagger.URL("http://localhost:8080/todo/v1/swagger/doc.json"), // URL para acessar a documentação do Swagger
)
}
Passo 8: Implementação da função main
Implemente a função main no arquivo main.go:
package main
import (
"log"
"net/http"
db "go-todo-api/src/config"
swagger "go-todo-api/src/config"
"go-todo-api/src/handlers"
"go-todo-api/src/repositories"
"go-todo-api/src/router"
)
// @title TODO API
// @version 1.0
// @description This is a sample todo api.
// @termsOfService http://swagger.io/terms/
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /todo/v1
// @externalDocs.description OpenAPI
// @externalDocs.url https://swagger.io/resources/open-api/
func main() {
// Inicializa conexão com o banco de dados
db.InitDB()
defer db.CloseDB()
// Configuração do repositório
repo := repositories.NewTodoRepository(db.DB)
// Configuração do handler
handler := &handlers.TodoHandler{
Repository: repo,
}
// Configuração do roteamento
r := router.NewRouter(handler)
// Configuração do Swagger
r.PathPrefix("/swagger/").Handler(swagger.SetupSwagger()).Methods(http.MethodGet)
// Inicialização do servidor
log.Println("Servidor rodando em http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
Passo 9: Gerando a documentação da API
As documentações das rotas se encotram no handler e a documentação da API está no main, vamos executar o comando abaixo para gerar os arquivos na pasta docs:
Instalação do cmd swag
go install github.com/swaggo/swag/cmd/swag@latest
Geração da documentação da API
swag init
Passo 10: Executando a aplicação
go run main.go
Acesse o Swagger em http://localhost:8080/todo/v1/swagger/index.html para visualizar e testar as operações CRUD da sua API TO-DO.
Agora você tem uma API em GO para uma aplicação de TO-DO, com Swagger para documentação e MySQL como banco de dados. Você pode usar esse exemplo como base e expandir conforme necessário para atender às suas necessidades.
O projeto deste post está no meu github link.