CRUD Golang: Criar Site com Upload de imagem Migrate SQLC Golang MySQL HTML Bootstrap Guest Post: Artigo por Washington Gomes @criarsite ...
Veja CRUD Golang: Criar Site com Upload de imagem Migrate SQLC Golang MySQL + HTML + Bootstrap
CRUD Golang: Criar Site com Upload de imagem Migrate SQLC Golang MySQL HTML Bootstrap
Vamos criar CRUD Golang, um exemplo de aplicação web em Go que envolve a criação de um banco de dados usando o mysql, migração, uso do SQLC para geração de código SQL e interação com o banco de dados. Vou guiá-lo passo a passo por cada etapa.
Ao longo deste guia passo a passo, abordaremos o desenvolvimento de um CRUD, a criação do banco de dados e tabelas por meio do Migrate, a geração de código SQL com o SQLC, e a interação com o banco de dados por meio do pacote sql do Go. Utilizaremos também o VSCode para depuração do código, proporcionando uma experiência de desenvolvimento mais eficiente.
Este projeto envolve não apenas o backend em Go, mas também a criação de páginas HTML utilizando templates, proporcionando uma experiência de usuário agradável. Além disso, abordaremos o upload e manipulação de imagens, agregando funcionalidades práticas à aplicação.
Prepare-se para mergulhar na criação de uma aplicação web completa em Go, explorando as boas práticas de desenvolvimento e utilizando ferramentas modernas para facilitar o processo. Ao final deste guia, você terá uma compreensão sólida de como construir uma aplicação web escalável e modular em Go, aproveitando recursos como Migrate e SQLC para uma interação eficiente com o banco de dados MySQL.
Para debugar seu codigo GO usando o vsCode va em run start debugging ou aperter F5 e inicializa o debug, caso ainda não tenha o arquivo launch.json dentro da pasta .vscode espere ser gerado ou crie manual e passe o seguinte comando
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}"
}
]
}
Para iniciar abra o terminal e inicie um modulo
go mod init modulo
Dica: crie as pastas models, views, controllers, uploads, wwwroot
dica: dentro da pasta uploads coloque uma imagem padrão que será carregada caso nenhuma imagem seja enviada.
1. Crie o Banco de Dados
Crie um banco de dados chamado blog usando o seu cliente de banco de dados mysql ou através da linha de comando:
CREATE DATABASE blog;
migrate create -ext=sql -dir=dal/migrations -seq inicio
DROP TABLE IF EXISTS artigos;
CREATE TABLE artigos (ID INT AUTO_INCREMENT PRIMARY KEY,titulo VARCHAR(100),descricao TEXT,imagem VARCHAR(250)criadoEm TIMESTAMP DEFAULT CURRENT_TIMESTAMP,editadoEm TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP);
migrate -path=dal/migrations -database "mysql://root:root@tcp(localhost:3306)/blog" -verbose up
migrate -path=dal/migrations -database "mysql://root:root@tcp(localhost:3306)/blog" -verbose down
version: "2"sql:- schema: "dal/migrations"queries: "dal/queries"engine: "mysql"gen:go:package: "repositorio"out: "dal/repositorio"
-- name: ListarArtigos :manySELECT * FROM artigos;-- name: ArtigoPorId :oneSELECT * FROM artigos WHERE id = ?;-- name: CriarArtigo :execINSERT INTO artigos (Titulo, Descricao, Imagem) VALUES (?, ?, ?);-- name: AtualizarArtigo :execUPDATE artigos SET Titulo = ?, Descricao = ?, Imagem = ? WHERE id = ?;-- name: DeletarArtigo :execDELETE FROM artigos WHERE id = ?;
db.go
models.go
artigos.sql.go
package dalimport ("database/sql""log"_ "github.com/go-sql-driver/mysql")func ConectarDB() *sql.DB {conectionString := "root:root@/blog?charset=utf8&parseTime=True&loc=Local"db, erro := sql.Open("mysql", conectionString)if erro != nil {log.Fatal(erro)}erro = db.Ping()if erro != nil {log.Fatal(erro)}return db}
go get github.com/go-sql-driver/mysql
package mainimport ("modulo/controllers""net/http")func main() {http.HandleFunc("/", controllers.Inicio)
http.Handle("/wwwroot/", http.StripPrefix("/wwwroot/", http.FileServer(http.Dir("wwwroot"))))
// http.HandleFunc("/criar", controllers.CriarArtigo)// http.HandleFunc("/listar", controllers.ListarArtigos)// http.HandleFunc("/deletar", controllers.Deletar)// http.HandleFunc("/detalhes", controllers.MostarArtigo)// http.HandleFunc("/editar", controllers.EditarArtigo)println("Rodando na porta http://localhost:8080")http.ListenAndServe(":8080", nil)}
-1- -------2------ --3-- ---4--- --5--http.HandleFunc("/", controllers.Inicio)
.imgHome{width: 200px;height: auto;}
package controllersimport ("io""log""math/rand""os""strconv""strings""time")func GerarCodigoUnico() string {// Obter o horário atual como semente para a geração de números aleatóriosseed := time.Now().UnixNano()rand.Seed(seed)// Alfabeto para escolher as letrasalphabet := "abcdefghijklmnopqrstuvwxyz"var codigo strings.Builderfor i := 0; i < 5; i++ {randomIndex := rand.Intn(len(alphabet))codigo.WriteByte(alphabet[randomIndex])}return codigo.String()}func EnviarImagem(file io.Reader, artigoID int) (string, error) {nomeImagemUnico := GerarCodigoUnico() + "_" + strconv.Itoa(artigoID) + ".jpg"path := "./uploads/" + nomeImagemUniconewFile, err := os.Create(path)if err != nil {log.Println("Erro ao criar o arquivo de imagem:", err)return "", err}defer newFile.Close()_, err = io.Copy(newFile, file)if err != nil {log.Println("Erro ao copiar a imagem para o servidor:", err)return "", err}return "/uploads/" + nomeImagemUnico, nil}
package controllersimport ("net/http""html/template")var tmpl = template.Must(template.ParseGlob("views/*"))func Inicio(w http.ResponseWriter, r *http.Request) {tmpl.ExecuteTemplate(w, "inicio", nil)}
inicio.tmplheader.tmplfooter.tmplmenu.tmpl
No arquivo inicio.tmpl
{{ define "inicio" }}{{ template "header" }}<div class="container"><div class="jumbotron mt-5"><h1 class="display-4">Bem-vindo à Página Inicial</h1><p class="lead">Este é um exemplo de uma landing page.</p><hr class="my-4"><p>Explore os produtos e serviços incríveis que oferecemos.</p><a class="btn btn-primary btn-lg" href="#" role="button">Saiba Mais</a></div><div class="row"><div class="col-md-4"><h2>Recursos</h2><p>Descubra os recursos incríveis que temos para oferecer.</p><a class="btn btn-secondary" href="#" role="button">Saiba Mais</a></div><div class="col-md-4"><h2>Serviços</h2><p>Explore nossos serviços excepcionais.</p><a class="btn btn-secondary" href="#" role="button">Saiba Mais</a></div><div class="col-md-4"><h2>Contato</h2><p>Fale conosco para obter mais informações.</p><a class="btn btn-secondary" href="#" role="button">Entre em Contato</a></div></div></div>{{ template "footer" }}{{ end }}
No arquivo header.tmpl
{{ define "header" }}<!doctype html><html lang="en"><head><title>Inicio</title><!-- Required meta tags --><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><!-- Bootstrap CSS --><link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"><link rel="stylesheet" href="./wwwroot/style.css"></head><body>{{template "menu"}}{{ end }}
No arquivo footer.tmpl
{{ define "footer" }}</body></html>{{ end }}
No arquivo menu.tmpl
{{define "menu"}}<nav class="navbar navbar-expand navbar-light bg-light"><div class="nav navbar-nav"><a class="nav-item nav-link active" href="/">Home <span class="sr-only">(current)</span></a><a class="nav-item nav-link" href="/">Inicio</a><a class="nav-item nav-link" href="/listar">Produtos</a><a class="nav-item nav-link" href="/criar">Cadastrar</a></div></nav>{{end}}
Exemplo Controller.go
func Exemplo(w http.ResponseWriter, r *http.Request) {// Verifica se a requisição é do método POSTif r.Method == http.MethodPost {// Lida com a requisição POST}//Codigo executado caso a requeste seja o metodo GETtmpl.ExecuteTemplate(w, "pagina", nil)}
var repo = repositorio.New(dal.ConectarDB())
func CriarArtigo(w http.ResponseWriter, r *http.Request) {if r.Method == http.MethodPost {if err := r.ParseMultipartForm(10 << 20); err != nil { // 10 << 20 é o tamanho máximo do formulário em bytes (10 MB)http.Error(w, "Erro ao analisar os dados do formulário", http.StatusBadRequest)return}titulo := r.FormValue("titulo")descricao := r.FormValue("descricao")file, _, err := r.FormFile("imagem")var imagem stringif err == nil {defer file.Close()imagem, err = EnviarImagem(file, 0)if err != nil {http.Error(w, "Erro ao realizar o upload da imagem", http.StatusInternalServerError)return}} else {// Se nenhuma nova imagem for enviada, use a imagem padrãoimagem = "/uploads/noimage.jpg"}// Crie o artigo com a imagem apropriadaartigo := repositorio.CriarArtigoParams{Titulo: titulo,Descricao: descricao,Imagem: imagem,}if err := repo.CriarArtigo(r.Context(), artigo); err != nil {http.Error(w, "Erro ao criar o artigo: "+err.Error(), http.StatusInternalServerError)return}http.Redirect(w, r, "/listar", http.StatusSeeOther)return} else {// Renderize o formulário de criaçãoif err := tmpl.ExecuteTemplate(w, "criar", nil); err != nil {http.Error(w, "Erro ao renderizar o formulário: "+err.Error(), http.StatusInternalServerError)return}}}
http.HandleFunc("/criar", controllers.CriarArtigo)
{{ define "criar" }}{{ template "header"}}<div class="card"><div class="card-header"> Novo Artigo </div><div class="card-body"><form action="/criar" method="post" enctype="multipart/form-data"><div class="form-group"><label for="titulo">Título</label><input type="text" class="form-control" name="titulo" id="titulo" aria-describedby="helpId" placeholder=""><small id="helpId" class="form-text text-muted">Escreva um título</small></div><div class="form-group"><label for="descricao">Descrição</label><input type="text" class="form-control" name="descricao" id="descricao" aria-describedby="helpId" placeholder=""><small id="helpId" class="form-text text-muted">Descrição</small></div><div class="form-group"><label for="imagem">Imagem</label><input type="file" class="form-control-file" name="imagem" id="imagem" aria-describedby="helpId"><small id="helpId" class="form-text text-muted">Escolha uma imagem</small></div><button type="submit" class="btn btn-success">Cadastrar</button><a class="btn btn-dark" href="/listar" role="button">Voltar</a></form></div></div>{{ template "footer" }}{{ end }}
<form action="/criar" method="post" enctype="multipart/form-data">
func ListarArtigos(w http.ResponseWriter, r *http.Request) {artigos, erro := repo.ListarArtigos(r.Context())if erro != nil {http.Error(w, "Erro ao carregar os artigos", http.StatusInternalServerError)return}// Ordenar usuários de forma decrescente pelo campo desejadosort.Slice(artigos, func(i, j int) bool {return artigos[i].ID > artigos[j].ID})erro = tmpl.ExecuteTemplate(w, "listar", artigos)if erro != nil {http.Error(w, "Erro ao renderizar o modelo ", http.StatusInternalServerError)return}}
http.HandleFunc("/listar", controllers.ListarArtigos)
{{ define "listar" }}{{ template "header"}}<br/><a name="" id="" class="btn btn-primary" href="/criar" role="button">Novo artigo</a><br/><br/><table class="table"><thead><tr><th>Imagem</th><th>Titulo</th><th>Descrição</th><th>Ações</th></tr></thead><tbody>{{ range . }}<tr><td> <img src="{{ .Imagem }}" class="imgHome" alt="{{ .Titulo }}"></td><td>{{ .Titulo }}</td><td>{{ .Descricao }}</td><td><a class="btn btn-info" href="/detalhes?id={{ .ID }}" role="button">Ler</a><a class="btn btn-warning" href="/editar?id={{ .ID }}" role="button">Editar</a><a class="btn btn-danger" href="/deletar?id={{ .ID }}" role="button">Deletar</a></td></tr>{{ end }}</tbody></table><br/>{{ template "footer" }}{{ end }}
go run main.go
func MostarArtigo(w http.ResponseWriter, r *http.Request) {id := r.FormValue("id")ID, err := strconv.Atoi(id)if err != nil {http.Error(w, "ID do artigo inválido", http.StatusBadRequest)return}artigo, err := repo.ArtigoPorId(r.Context(), int32(ID))if err != nil {http.Error(w, "Erro ao buscar o artigo", http.StatusInternalServerError)return}erro := tmpl.ExecuteTemplate(w, "detalhes", artigo)if erro != nil {http.Error(w, "Erro ao renderizar o usuário", http.StatusInternalServerError)return}}
http.HandleFunc("/detalhes", controllers.MostarArtigo)
{{ define "detalhes" }}{{ template "header" }}<h2> Produto {{ .ID }} </h2><td> <img src="{{ .Imagem }}" class="imgshow" alt="{{ .Titulo }}"></td><p> {{ .Titulo }}</p><p> {{ .Descricao }}</p><br /><div><a class="btn btn-warning" href="/editar?id={{ .ID }}">Editar</a><a class="btn btn-dark" href="/">Voltar</a></div>{{ template "footer" }}{{ end }}
go run main.go
func EditarArtigo(w http.ResponseWriter, r *http.Request) {if r.Method == http.MethodPost {if err := r.ParseMultipartForm(10 << 20); err != nil {http.Error(w, "Erro ao analisar os dados do formulário", http.StatusBadRequest)return}id := r.FormValue("id")artigoID, err := strconv.Atoi(id)if err != nil {http.Error(w, "ID do artigo inválido", http.StatusBadRequest)return}titulo := r.FormValue("titulo")descricao := r.FormValue("descricao")file, _, err := r.FormFile("imagem")var imagem stringif err == nil {defer file.Close()imagem, err = EnviarImagem(file, artigoID)if err != nil {http.Error(w, "Erro ao realizar o upload da imagem", http.StatusInternalServerError)return}} else {artigoAntigo, err := repo.ArtigoPorId(r.Context(), int32(artigoID))if err != nil {http.Error(w, "Erro ao obter os dados do artigo", http.StatusInternalServerError)return}imagem = artigoAntigo.Imagem}artigoAtualizado := repositorio.AtualizarArtigoParams{Titulo: titulo,Descricao: descricao,Imagem: imagem,ID: int32(artigoID),}if err := repo.AtualizarArtigo(r.Context(), artigoAtualizado); err != nil {http.Error(w, "Erro ao atualizar o artigo", http.StatusInternalServerError)return}http.Redirect(w, r, "/listar", http.StatusSeeOther)return} else {id := r.FormValue("id")artigoID, err := strconv.Atoi(id)if err != nil || artigoID <= 0 {http.Error(w, "ID do artigo inválido", http.StatusBadRequest)return}artigo, err := repo.ArtigoPorId(r.Context(), int32(artigoID))if err != nil {http.Error(w, "Erro ao obter os dados do artigo", http.StatusInternalServerError)return}if err := tmpl.ExecuteTemplate(w, "editar", artigo); err != nil {http.Error(w, "Erro ao renderizar o formulário", http.StatusInternalServerError)return}}}
http.HandleFunc("/editar", controllers.EditarArtigo)
{{ define "editar" }}{{ template "header"}}<div class="card"><div class="card-header"> Editar Artigo </div><div class="card-body"><form action="/editar?id={{.ID}}" method="post" enctype="multipart/form-data"><input type="hidden" value="{{.ID}}" name="id"><div class="form-group"><label for="titulo">Título</label><input value="{{ .Titulo }}" type="text" class="form-control" name="titulo" id="titulo" aria-describedby="helpTitulo" placeholder=""><small id="helpTitulo" class="form-text text-muted">Escreva um título</small></div><div class="form-group"><label for="descricao">Descrição</label><input type="text" value="{{ .Descricao }}" class="form-control" name="descricao" id="descricao" aria-describedby="helpDescricao" placeholder=""><small id="helpDescricao" class="form-text text-muted">Descrição</small></div><div class="form-group row"><label for="imagem" class="col-sm-2 col-form-label">Imagem</label><div class="col-sm-6"><input type="file" class="form-control-file" name="imagem" id="imagem" aria-describedby="helpImagem"><small id="helpImagem" class="form-text text-muted">Escolha uma imagem</small></div><div class="col-sm-4"><img src="{{ .Imagem }}" class="imgHome float-right" alt="{{ .Titulo }}"></div></div><button type="submit" class="btn btn-success">Salvar</button><a class="btn btn-dark" href="/listar" role="button">Voltar</a></form></div></div>{{ template "footer" }}{{ end }}
go run main.go
http.HandleFunc("/deletar", controllers.Deletar)
go run main.go
Gostou da CRUD Golang: Criar Site com Upload de imagem Migrate SQLC Golang MySQL + HTML + Bootstrap Não se esqueça de curtir e compartilhar com seus amigos o CRUD Golang: Criar Site com Upload de imagem Migrate SQLC Golang MySQL + HTML + Bootstrap para apoiar o nosso trabalho!
COMMENTS