Implementando CRUD com slices no Gin

Introdução

Este tutorial demonstra como construir uma API simples para gerenciamento de receitas culinárias utilizando o framework Gin e armazenando os dados em um slice do Go.

Modelo de Dados e Endpoints da API

Modelo

type Refeicao struct {
    Nome          string    `json:"nome"`
    Tags          []string  `json:"tags"`
    Componentes   []string  `json:"componentes"`
    Passos        []string  `json:"passos"`
    DataPublicacao time.Time `json:"dataPublicacao"`
}

Endpoints

Método HTTP Recurso Descrição
GET /refeicoes Lista todas as reecitas
POST /refeicoes Cria uma nova receita
PUT /refeicoes/:id Atualiza uma receita existente
DELETE /refeicoes/:id Remove uma receita existente
GET /refeicoes/buscar?tag=X Busca receitas por tag

Implementação dos Handlers

Criando uma nova receita

POST /refeicoes

package main

import (
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/rs/xid"
)

type Refeicao struct {
    ID             string    `json:"id"`
    Nome           string    `json:"nome"`
    Tags           []string  `json:"tags"`
    Componentes    []string  `json:"componentes"`
    Passos         []string  `json:"passos"`
    DataPublicacao time.Time `json:"dataPublicacao"`
}

var listaRefeicoes []Refeicao

func init() {
    listaRefeicoes = make([]Refeicao, 0)
}

func CriarRefeicaoHandler(ctx *gin.Context) {
    var novaRefeicao Refeicao
    if err := ctx.ShouldBindJSON(&novaRefeicao); err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
            "erro": err.Error(),
        })
        return
    }

    novaRefeicao.ID = xid.New().String()
    novaRefeicao.DataPublicacao = time.Now()
    listaRefeicoes = append(listaRefeicoes, novaRefeicao)
    ctx.JSON(http.StatusOK, novaRefeicao)
}

func main() {
    roteador := gin.Default()
    roteador.POST("/refeicoes", CriarRefeicaoHandler)
    roteador.Run("127.0.0.1:8080")
}

Testando com Python

import requests
import json

def testar_criacao(dados):
    url = "http://127.0.0.1:8080/refeicoes"
    resposta = requests.post(url=url, data=json.dumps(dados))
    print("Teste de criação:")
    print(resposta.text)

if __name__ == "__main__":
    dados_receita = {
        "nome": "Pizza Caseira",
        "tags": ["italiana", "pizza", "jantar"],
        "componentes": [
            "1 e 1/2 xícara (355 ml) água morna",
            "1 pacote de fermento biológico seco",
            "3 e 3/4 xícara de farinha de pão",
            "queijo mussarela ralado",
        ],
        "passos": [
            "Passo 1.",
            "Passo 2.",
            "Passo 3.",
        ],
    }
    testar_criacao(dados_receita)

Listando todas as receitas

GET /refeicoes

func ListarRefeicoesHandler(ctx *gin.Context) {
    ctx.JSON(http.StatusOK, listaRefeicoes)
}

func main() {
    roteador.GET("/refeicoes", ListarRefeicoesHandler)
}

Atualizando uma receita existente

PUT /refeicoes/:id

func AtualizarRefeicaoHandler(ctx *gin.Context) {
    id := ctx.Param("id")
    var refeicaoAtualizada Refeicao
    
    if err := ctx.ShouldBindJSON(&refeicaoAtualizada); err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
            "erro": err.Error(),
        })
        return
    }

    indice := -1
    for i := 0; i < len(listaRefeicoes); i++ {
        if listaRefeicoes[i].ID == id {
            indice = i
            break
        }
    }

    if indice == -1 {
        ctx.JSON(http.StatusNotFound, gin.H{
            "erro": "Refeição não encontrada",
        })
        return
    }

    refeicaoAtualizada.ID = id
    listaRefeicoes[indice] = refeicaoAtualizada
    ctx.JSON(http.StatusOK, refeicaoAtualizada)
}

func main() {
    roteador.PUT("/refeicoes/:id", AtualizarRefeicaoHandler)
}

Excluindo uma receita

DELETE /refeicoes/:id

func ExcluirRefeicaoHandler(ctx *gin.Context) {
    id := ctx.Param("id")
    indice := -1

    for i := 0; i < len(listaRefeicoes); i++ {
        if listaRefeicoes[i].ID == id {
            indice = i
            break
        }
    }

    if indice == -1 {
        ctx.JSON(http.StatusNotFound, gin.H{
            "erro": "Refeição não encontrada",
        })
        return
    }

    listaRefeicoes = append(listaRefeicoes[:indice], listaRefeicoes[indice+1:]...)

    ctx.JSON(http.StatusOK, gin.H{
        "mensagem": "Refeição foi excluída com sucesso",
    })
}

func main() {
    roteador.DELETE("/refeicoes/:id", ExcluirRefeicaoHandler)
}

Buscando receitas por tag

GET /refeicoes/buscar?tag=X

import "strings"

func BuscarRefeicaoHandler(ctx *gin.Context) {
    tag := ctx.Query("tag")
    resultados := make([]Refeicao, 0)

    for i := 0; i < len(listaRefeicoes); i++ {
        encontrada := false
        for _, t := range listaRefeicoes[i].Tags {
            if strings.EqualFold(t, tag) {
                encontrada = true
                break
            }
        }

        if encontrada {
            resultados = append(resultados, listaRefeicoes[i])
        }
    }

    ctx.JSON(http.StatusOK, resultados)
}

func main() {
    roteador.GET("/refeicoes/buscar", BuscarRefeicaoHandler)
}

Código Completo

package main

import (
    "net/http"
    "strings"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/rs/xid"
)

type Refeicao struct {
    ID             string    `json:"id"`
    Nome           string    `json:"nome"`
    Tags           []string  `json:"tags"`
    Componentes    []string  `json:"componentes"`
    Passos         []string  `json:"passos"`
    DataPublicacao time.Time `json:"dataPublicacao"`
}

var listaRefeicoes []Refeicao

func init() {
    listaRefeicoes = make([]Refeicao, 0)
}

func CriarRefeicaoHandler(ctx *gin.Context) {
    var novaRefeicao Refeicao
    if err := ctx.ShouldBindJSON(&novaRefeicao); err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
            "erro": err.Error(),
        })
        return
    }

    novaRefeicao.ID = xid.New().String()
    novaRefeicao.DataPublicacao = time.Now()
    listaRefeicoes = append(listaRefeicoes, novaRefeicao)
    ctx.JSON(http.StatusOK, novaRefeicao)
}

func ListarRefeicoesHandler(ctx *gin.Context) {
    ctx.JSON(http.StatusOK, listaRefeicoes)
}

func AtualizarRefeicaoHandler(ctx *gin.Context) {
    id := ctx.Param("id")
    var refeicaoAtualizada Refeicao

    if err := ctx.ShouldBindJSON(&refeicaoAtualizada); err != nil {
        ctx.JSON(http.StatusBadRequest, gin.H{
            "erro": err.Error(),
        })
        return
    }

    indice := -1
    for i := 0; i < len(listaRefeicoes); i++ {
        if listaRefeicoes[i].ID == id {
            indice = i
            break
        }
    }

    if indice == -1 {
        ctx.JSON(http.StatusNotFound, gin.H{
            "erro": "Refeição não encontrada",
        })
        return
    }

    refeicaoAtualizada.ID = id
    listaRefeicoes[indice] = refeicaoAtualizada
    ctx.JSON(http.StatusOK, refeicaoAtualizada)
}

func ExcluirRefeicaoHandler(ctx *gin.Context) {
    id := ctx.Param("id")
    indice := -1

    for i := 0; i < len(listaRefeicoes); i++ {
        if listaRefeicoes[i].ID == id {
            indice = i
            break
        }
    }

    if indice == -1 {
        ctx.JSON(http.StatusNotFound, gin.H{
            "erro": "Refeição não encontrada",
        })
        return
    }

    listaRefeicoes = append(listaRefeicoes[:indice], listaRefeicoes[indice+1:]...)
    ctx.JSON(http.StatusOK, gin.H{
        "mensagem": "Refeição foi excluída com sucesso",
    })
}

func BuscarRefeicaoHandler(ctx *gin.Context) {
    tag := ctx.Query("tag")
    resultados := make([]Refeicao, 0)

    for i := 0; i < len(listaRefeicoes); i++ {
        encontrada := false
        for _, t := range listaRefeicoes[i].Tags {
            if strings.EqualFold(t, tag) {
                encontrada = true
                break
            }
        }

        if encontrada {
            resultados = append(resultados, listaRefeicoes[i])
        }
    }

    ctx.JSON(http.StatusOK, resultados)
}

func main() {
    roteador := gin.Default()
    roteador.POST("/refeicoes", CriarRefeicaoHandler)
    roteador.GET("/refeicoes", ListarRefeicoesHandler)
    roteador.PUT("/refeicoes/:id", AtualizarRefeicaoHandler)
    roteador.DELETE("/refeicoes/:id", ExcluirRefeicaoHandler)
    roteador.GET("/refeicoes/buscar", BuscarRefeicaoHandler)
    roteador.Run("127.0.0.1:8080")
}

Tags: go gin rest-api CRUD web-development

Publicado em 6-13 23:11 por Thomas