CRUD Golang: Criar Site com Upload de imagem Migrate SQLC Golang MySQL + HTML + Bootstrap - Ver e Fazer CRUD Golang: Criar Site com Upload de imagem Migrate SQLC Golang MySQL + HTML + Bootstrap | Ver e Fazer

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  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 


Guest Post: Artigo por Washington Gomes @criarsite

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;

2. Agora vamos usar o migrate para gerar a migração para criar as tabelas no bando de dados. 
no terminal rode o comando.

 migrate create -ext=sql -dir=dal/migrations -seq inicio
Então, quando você executa este comando, a ferramenta migrate criará um novo arquivo de migração com o nome seguindo a sequência definida, usando a extensão especificada, e salvará esse arquivo no diretório especificado. Esse arquivo será usado para escrever as instruções SQL que representam as alterações que você deseja aplicar ao seu banco de dados durante a migração.

Porque DAL?

No arquivo .down.sql coloca o codigo que sera responsavel por deletar a tabela no banco de dados, isso sera necessario caso queira reverter alguma migração. 

DROP TABLE IF EXISTS artigos;

no arquivo .up.sql coloca o codigo que será usado para criar a tabela no banco de dados. 

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
);

3. rode o comando que vai gerar a migração 

Para gerar a tabela no banco de dados Migrate UP

migrate -path=dal/migrations -database "mysql://root:root@tcp(localhost:3306)/blog" -verbose up 

Para remover uma migração feita no banco de dados Migrate Down

migrate -path=dal/migrations -database "mysql://root:root@tcp(localhost:3306)/blog" -verbose down  

Agora com o banco de dados e a tabela criada. 

4. Na raiz do projeto crie um arquivo chamado sqlc.yaml 


version: "2"
sql:
- schema: "dal/migrations"
  queries: "dal/queries"
  engine: "mysql"
  gen:
    go:
      package: "repositorio"
      out: "dal/repositorio"
5. Dentro da pasta DAL crie uma pasta chamada queries e dentro desta pasta um arquivo com o nome da tabela que tem no bando de dados com a estensao .sql  dal/queries/artigos.sql

dentro deste arquivo artigos.sql que sera o arquivo lido pelo SQLC

-- name: ListarArtigos :many
SELECT * FROM artigos;

-- name: ArtigoPorId :one
SELECT * FROM artigos  WHERE id = ?;

-- name: CriarArtigo :exec
INSERT INTO artigos (Titulo, Descricao, Imagem) VALUES (?, ?, ?);

-- name: AtualizarArtigo :exec
UPDATE artigos SET Titulo = ?, Descricao  = ?, Imagem = ?  WHERE id = ?;

-- name: DeletarArtigo :exec
DELETE FROM artigos WHERE id = ?;

Sera gerado uma pasta conforme especificado no arquivo sqlc.yaml 
a pasta repositorio dentro da pasta dal contem 3 arquivos. models.go, db.go artigos.sql.go 

db.go 


Essencialmente, este arquivo fornece uma estrutura básica para interação com o banco de dados, com métodos que podem ser chamados para executar consultas SQL no contexto de uma transação. O SQLC usa essa estrutura como base para gerar código específico para cada consulta definida no arquivo SQLC.

models.go


Essencialmente, esta struct Artigo é gerada pelo SQLC com base nas colunas da tabela "artigos" no banco de dados. Será usada para mapear os resultados das consultas SQL em objetos Go no código da aplicação. Os campos da struct correspondem diretamente às colunas do banco de dados, permitindo um fácil mapeamento de dados entre a aplicação Go e o banco de dados.

artigos.sql.go 

Essencialmente, esse arquivo representa a interface entre o código Go da aplicação e as consultas SQL definidas no arquivo "artigos.sql". As funções geradas encapsulam a execução das consultas e a manipulação dos resultados no formato desejado pela aplicação.

6. Dentro da pasta DAL  crie um arquivo conexao.go e crie a funcao que faz a conexão com o banco de dados MySQL 

package dal

import (
"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

}

Vai ser preciso importar o pacote do mysql; 
No terminal digite 
go get github.com/go-sql-driver/mysql

7. Crie um arquivo na raiz do projeto  chamado main.go

dentro da funcao main 

package main

import (
"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)
}


obs; para cada função que criar, remova do comentario HandleFunc.

Entenda o codigo
-1-     -------2------   --3--     ---4---     --5--
http.HandleFunc("/", controllers.Inicio)

O trecho de código http.HandleFunc("/criar", controllers.CriarArtigo) é uma linha de código em Go que configura um manipulador (handler) para a rota "/criar" em um servidor HTTP. Vamos detalhar o que cada parte faz:

http.HandleFunc: Esta função é parte do pacote http em Go e é usada para registrar um manipulador para uma rota específica.

"/criar": Este é o caminho (rota) que o manipulador será associado. Isso significa que quando uma solicitação HTTP é feita para "/criar", o manipulador controllers.CriarArtigo será chamado para processar a solicitação.

controllers.CriarArtigo: Esta é a função (ou manipulador) que será chamada quando uma solicitação é feita para a rota "/criar". Essa função está definida no pacote controllers do seu código.

Em termos simples, esta linha de código configura um manipulador para a rota "/criar", indicando que a função controllers.CriarArtigo deve ser chamada sempre que uma solicitação HTTP for feita para essa rota específica. O manipulador é responsável por processar e responder adequadamente a essa solicitação, por exemplo, lidar com a criação de um novo artigo no contexto da sua aplicação web.

obs: dentro da pasta wwwroot crie o arquivo style.css 
.imgHome{
    width: 200px;
    height: auto;
}

8. Dentro da pasta controller crie duas controllers. imagemController.go, viewController.go e artigosController.go 
dentro da viewsController.go sera passado o que para paginas simples como pagina inicial, contato, sobre etc. 

imagemController.go: esta controller sera responsavel por salvar a imagem na pasta uploads

 
package controllers

import (
"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órios
seed := time.Now().UnixNano()
rand.Seed(seed)

// Alfabeto para escolher as letras
alphabet := "abcdefghijklmnopqrstuvwxyz"

var codigo strings.Builder
for 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/" + nomeImagemUnico

newFile, 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
}


viewsController.go

package controllers

import (
"net/http"
"html/template"
)

var tmpl = template.Must(template.ParseGlob("views/*"))

func Inicio(w http.ResponseWriter, r *http.Request) {
tmpl.ExecuteTemplate(w, "inicio", nil)
}

Na posta views crie os arquivos para cada parte do codigo html 

inicio.tmpl 
header.tmpl 
footer.tmpl 
menu.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 POST
    if r.Method == http.MethodPost {
        // Lida com a requisição POST
     }

//Codigo executado caso a requeste seja o metodo GET
tmpl.ExecuteTemplate(w, "pagina", nil)
}

Vamos começar a criar o site a controller, rota e views. são 3 passos. 
Criar a Funcao
Criar a Rota 
Criar a Views com o formulario que que acessara a rota. 

na artigoController.go crie a funcao CriarArtigo 
na main.go crie a rota que sera passado dentro da funcao
na pasta views crie um o html com o formulario com os campos serão usado para salva no banco de dados 

artigosController.go

cria uma instância do repositório (repo) usando a função New do pacote repositorio. passando sua funcão de conexão com o banco de dados
var repo = repositorio.New(dal.ConectarDB())
 
Em resumo, este código configura uma única instância do repositório que utiliza a conexão do banco de dados obtida pela função ConectarDB no pacote dal. Essa instância do repositório será usada para realizar operações no banco de dados ao longo do ciclo de vida da aplicação.

Função CriarArtigo

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 string

if 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ão
imagem = "/uploads/noimage.jpg"
}

// Crie o artigo com a imagem apropriada
artigo := 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ção
if err := tmpl.ExecuteTemplate(w, "criar", nil); err != nil {
http.Error(w, "Erro ao renderizar o formulário: "+err.Error(), http.StatusInternalServerError)
return
}
}
}

Na main.go defina a rota  
 http.HandleFunc("/criar", controllers.CriarArtigo)
Na pasta views crie o arquivo criar.tmpl

{{ 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 }}

Note este trecho de codigo no formulario   
 <form action="/criar" method="post" enctype="multipart/form-data">
é uma marcação HTML que define um formulário em uma página da web. Aqui está a explicação de cada parte:

<form>: Este é o elemento HTML usado para criar formulários. Ele define a área em que os elementos de formulário, como caixas de texto, botões, etc., serão colocados.

action="/criar": O atributo action especifica para onde os dados do formulário serão enviados quando o formulário for enviado. Neste caso, os dados do formulário serão enviados para a rota "/criar", foi a rota que você definiu na HandleFunc na main.go 

method="post": O atributo method especifica o método HTTP a ser usado ao enviar o formulário. Neste caso, o método POST será usado. Isso geralmente é usado quando você está enviando dados sensíveis ou grandes volumes de dados.

enctype="multipart/form-data": O atributo enctype especifica como os dados do formulário devem ser codificados antes de serem enviados para o servidor. multipart/form-data é usado quando o formulário contém um elemento <input type="file">, indicando que o formulário está sendo usado para upload de arquivos.

Portanto, esse trecho de código define um formulário que, quando enviado, envia os dados para a rota "/criar" usando o método POST e permite o envio de arquivos (como imagens, por exemplo). Isso é comumente usado em formulários que envolvem upload de arquivos.


Função ListarArtigos 

artigosController.go 

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 desejado
sort.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
}
}

Na main.go defina a rota  
http.HandleFunc("/listar", controllers.ListarArtigos)
Na pasta views crie um arquivo chamdo listar.tmpl 
{{ 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 }}
Rode o codigo  e verifique se esta funcionando conforme o esperado.
go run main.go
Função MostrarPorId 

artigosController.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
}
}

Na main.go crie a rota 
http.HandleFunc("/detalhes", controllers.MostarArtigo)
Na pasta views crie um arquivo detalhes.tmpl 


{{ 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 }}
Rode o codigo  e verifique se esta funcionando conforme o esperado.
go run main.go

Função EditarArtigo

artigosController.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 string

if 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
}
}
}

Na main.go crie a rota 
http.HandleFunc("/editar", controllers.EditarArtigo)

Na pasta views cria um arquivo editar.tmpl com o formalario que vai vir com os campos populados com os dados do artigo que deseja editar. 


{{ 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 }}
Rode o codigo  e verifique se esta funcionando conforme o esperado.
go run main.go

Função DeletarArtigo 

artigosController.go 

func Deletar(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
// Obter o ID do produto a ser deletado
id := r.FormValue("id")

// Validar se o ID é um número inteiro
artigoId, err := strconv.Atoi(id)
if err != nil {
http.Error(w, "ID do artigo inválido", http.StatusBadRequest)
return
}

// Lógica para deletar o produto do banco de dados
if err := repo.DeletarArtigo(r.Context(), int32(artigoId)); err != nil {
http.Error(w, "Erro ao deletar o artigo", http.StatusInternalServerError)
return
}

// Redirecionar para a página principal ou outra página apropriada
http.Redirect(w, r, "/listar", http.StatusSeeOther)
return
} else {
// Renderizar a página de confirmação/exclusão
tmpl.ExecuteTemplate(w, "/listar", nil)
}
}

Na main.go defina a rota 
http.HandleFunc("/deletar", controllers.Deletar)



Rode o codigo  e verifique se esta funcionando conforme o esperado.
go run main.go


Conclusão:
Prontinho temos um exemplo de aplicação web usando Migrate + SQLC + Golang + MySQL + HTML + Bootstrap, gostou? ficou com alguma duvida? 








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

Nome

3d,28,A bela e a Fera,3,Abecedario,110,Abelha,69,Abobora,1,Acessorios,11,Ads,40,AdSense,44,Adwords,1,Aeronaves,2,Afiliados,27,Agulheiros,21,Alfabeto,104,Alfineteiras,28,Alice,4,Alice no pais das maravilhas,6,Alimentos,1,Almofadas,129,Aluminio,1,Amigurumi,283,Animal,43,Animes,91,Aniversario,6,Anjinhos,55,Anuncios,5,Aparador de canecas,10,Aplicativos,6,Apliques,45,Apostilas,141,Aranha,2,Arco iris,2,Arranjos,5,Art,20,Arte,82,Arteiras,22,Artes,55,Artesanato,5895,artesanato Croche,2,Artigos,795,Artisanat,162,Arvores,27,Astronauta,6,Astronave,3,Atividades educativas,156,Atividades para educação infantil,181,Atividades para imprimir,139,Autoestima,1,Aves,2,Aviões,2,Babador,3,Babuska,1,Baby,30,Bailarina,52,Baixar,4,Balão,18,Baleia,8,Bandeirinha,54,Bandeirinhas,61,Banheiro,3,Barbante,3,Barcos,1,Base,1,Bastidor,49,Batizados,6,Bebê,43,Beleza,2,Bendy Dolls,1,Bichinhos,1809,Bichos,113,Bidu,1,Bijuterias,1,Bird,1,Biscuit,20,BJD,1,Blog,54,Blogger,43,Blusa,3,Boa tarde,1,Boas ideias,28,Bode,1,Boinas,2,Bola,4,Boleros,1,Bolo,7,Bolo Fake,3,Bolsa,108,Bolsas,8,Bom dia,9,Boneca,445,Boneca de pano,332,Boneca lol,61,Bonecas,933,Bonecos,392,Bonecos de neve,57,Book,1,Borboletas,23,Bordado,141,Bota,31,Botinha,10,Botões,3,Branca de Neve,12,Bricolagem,2,Brindes,10,Brinquedos,18,Bruxa,7,Bruxinha,35,Buque,1,Cabelo,2,Cabra,1,Caça palavras,1,Cachecol,2,Cachorrinhos,91,Cacto,12,Caderno,2,Caixa de leite,1,Caixas,29,Calça,3,Calendario,3,Canguru,2,Capa de Caderno,47,Capas,28,Capinhas para celular,6,Capitao america,2,Caracol,2,Carnaval,5,Carregador,1,Carreira,5,Carrinho,4,Carro,11,Carruagem,4,Carteira,3,Casa,23,Casacos,3,Casal,5,Casamento,3,Castelo,4,Castor,1,Cats,3,Cavalo,16,Cavalo marinho,1,Cegonha,4,Celular,2,Cenoura,1,Centopeia,7,Centro de mesa,40,Cervos,5,Cestas,19,Cha de bebe,34,Chapeu,3,Chaveiro,29,Chaveiros,84,Chinelos,2,Cinema,1,Circo,17,Cisne,3,Clash Royale,1,Coala,3,Cobra,2,Coelho,75,Coelhos,484,Cogumelo,3,Colorir,83,Comidas,2,Como faço,47,Como Fazer,272,Comunhão,1,Construção,1,Contos,1,Contos de Fadas,3,Coração,26,Coroa,6,Corretoras,1,Corte,23,Corte e costura,116,Cortinas,5,Coruja,69,Cosplay,1,Costura,40,Cozinha,7,Crafts,1074,Credito,22,Crianca,32,Croche,584,CupCake,10,Curiosidades,1,Cursos,39,Customização,1,Dado,3,Daisy,1,Deadpool,1,Decoração,406,Decorar,47,Decoupage,2,Dedoches,14,Dente,6,Dentista,5,Desenhos,488,Desenhos para Colorir,437,Dev,92,Dia dos Namorados,10,Diadema,1,Dicas,277,Dicas para blog,44,Dinossauros,40,Disney,83,Diy,85,Diy Ideas,15,Doces,6,Docinhos,8,Dog,3,Doki,1,Doll,54,Dragao,10,Dragon Ball,5,Duende,20,Duendes,62,E.V.A,54,Economia,17,Educação Infantil,239,Educar,108,Educativo,12,Elefante,44,Emoticons,2,Emprestimo,1,Enfeite,33,Enfeites,558,Envelope,1,Enxoval,1,Escola,21,Esconde chave,5,Espanhol,10,Espantalho.,7,Esquilos,7,Estilo,1,Estojo,22,Estrelas,29,Etiquetas,1,Eucaristia,2,EVA,472,Eventos,7,Expressoes,1,Fada,15,Fadinha,14,Fantoches,15,Farol,1,Fashion,6,Faz Facil,3,Fazenda,4,Fazendinha,6,Felt,401,Felting,1,Feltmania,24,Feltragem,27,Feltragem com agulhas,4,Feltreiras,15,Feltro,2489,Feltro 3d,2,Feltro Felt,1,Ferramentas,1,Festa,68,Fieltro,885,Filtro dos sonhos,2,Fimo,2,Finanças,38,Fios,1,Fita,1,Flamingos,4,Flamula,56,Flor,141,Flores,118,Floresta,3,Fofos,1,Foguete,1,Folhas,5,Forex,12,Formatura,6,Formiga,1,Fotos,3,Frances,3,Franjas,1,Frases,108,Frida Kahlo,1,Frutas,27,Fundo,2,Fundo do Mar,16,Fuxico,24,Galinha,33,Ganhar Dinheiro,41,Ganso,2,Gatinhos,146,Gato,50,Girafa,21,Golfinhos,3,Goma,34,Google,5,Gorros,4,Grafico,29,Graficos,80,gratuito,4,Guarda Chuva,2,Guaxinim,2,Guirlandas,137,Halloween,30,Handmade,189,Handwierker,167,Hello Kitty,6,Heroinas,7,Herois,11,Hipopotamo,9,Historia,1,Homem,1,Homem de ferro,2,Hora de Aventura,1,Host,3,Hulk,1,Humor,2,Ideias,151,Ideias para festa,38,Imagem,1,Imagens,12,imprimir,4,Ingles,2,Insetos,2,Investimento,15,Jacare,4,Jardim,5,Jinx,2,Joaninha,95,Jogo de banheiro,9,Jogos,16,Kawaii,28,Kimono,1,Koala,2,La,11,Laco,12,Laços,21,Lampada,1,Lanches,2,Latas,1,Layout,1,Leao,14,Legumes,2,Lembrancinhas,404,Letras,128,Lhama,11,Ligar os pontos,11,Lilo Stitch,1,Linhas,9,Livro,30,Lobo,1,lol,59,Lua,8,Luva,6,Macaco,10,Macrame,14,Makeup,1,Mandala,1,Manta,6,Manualidades,5293,Maquiagem,1,Marca Paginas,24,Marinheiro,7,Marketing,38,Marvel,1,Mascara,31,Mascaras,480,Máscaras,10,Matematica,16,Maternal,2,Matrioska,4,MDF,1,Meias,17,Meio ambiente,1,Memes,2,Meninas,34,mensagem,7,Mensagens,89,Mercado financeiro,6,Metoo,3,Mickey,19,Mimin dolls,3,Mimos,16,Mingau,1,Minie,12,Minion,4,Minnie,2,Mobile,40,Mochila,15,Moda,19,Modelos,67,Molde,73,Moldes,9914,Moldes 3d,15,Moldes de feltro,1364,Moldes de Silicone,1,Moldes em feltro,313,Moldes para eva,224,Moldes para feltro,940,Molds,7,Molduras,3,Monograma,6,Monstrinhos,2,Moranguinho,10,Motivação,1,Motivos,6,Mulher,16,Muñeca,3,Munecas,19,Mural,22,Musicas,2,Nail-Art,9,Naninha,12,Natal,1457,Navios,2,Necessaire,14,Noivos,3,Novidades,4,Numeros,14,Nuvem,11,Organizadores,1,Origami,3,Orquidea,3,Os Flintstones,1,Osito,1,Ourico,1,Outono,1,Ovelha,21,Ovo,4,Ovos,6,Painel,29,Paisagem,1,Palhaço,16,Palitos,1,Panda,25,Pano,65,Panos de prato,1,Pantufas,14,Papel,12,Papel de parede,11,Pascoa,173,páscoa,1,Passarinhos,55,Passo a passo,2,Patchwork,86,Pateta,1,Patinho,10,Pato,7,Pato Donald,1,Patrones,27,Patrulha Canina,4,Patterns,24,Pegasus,2,Peixes,10,Pelicano,1,Pelucia,41,Pena,2,Penteados,1,Peppa pig,2,Personagens,80,Personalização,2,Peru,1,Peso de porta,27,Peter pan,1,Pets,17,Piadas,1,Picole,1,Pingentes,54,Pinguim,13,Pinoquio,1,Pintar,75,Pintura,116,Pintura em tecido,122,Pirata,5,Placas,3,Plantas,5,Plantilla,3,Pluto,1,Pocket,1,Polvo,4,Ponei,1,Ponteira,7,Pontilhados,20,Ponto Cruz,20,Porquinha,1,Porta agenda,1,Porta agulhas,4,Porta Aliança,2,Porta copos,6,Porta docinhos,13,Porta fone,1,Porta Maternidade,44,Porta moedas,13,Porta oculos,1,Porta pano de pratos,9,Porta retratos,17,Porta trecos,10,Portugal,14,Prendedor de cabelo,2,Prendedor de Cortina,8,Presentes,34,Presepio,7,Primavera,1,Princesa,66,Principe,9,Protetor de Berço,17,Publicidade,2,Puff,10,Pulseiras,4,Quadro,37,Quarto,12,Quarto de bebê,20,Quarto de Criança,17,Quebra Cabeça,3,Quiet Book,38,Raposa,29,Ratinhos,24,Rato,5,Receitas,30,Reciclagem,21,Recrutamento,5,Rei,3,Relogio,4,Rena,148,Retalhos,1,Revista,73,Riscos,139,Robo,2,Rosa,12,Rosita,1,Roupas,58,Sache,1,Sacolinhas,16,Safari,25,Sala,1,Sansao,1,Santinhas,8,Sapatinhos,32,Sapinhos,12,Sapo,11,Saquinhos,13,Saúde,1,Scrap,13,Scrapbook,10,Script,1,Seguro,5,SEO,19,Sereia,17,Serie,4,Shorts,6,Show da Luna,1,Silicone,1,Slider,27,Sol,1,Sonic,2,Sorvete,3,Star wars,3,Surpresa,4,Tapete,28,Tartarugas,10,Tear,3,Tecido,176,Teia,1,Tela,1,Tema,4,Tenis,8,Terrarios,1,Thor,1,Tiara,9,Tigre,2,Tildas,4,Toalhas,5,Toalhinhas de mao,4,Toca,1,Topo de bolo,5,Torre,1,Torta,1,Torta Fake,1,Touca,5,Toy Story,2,Toys,1,Trabalhos manuais,639,Trader,2,Trafego,10,Trança,1,Travesseiros,3,Trico,28,Tucano,1,Turismo,2,Turma,11,Turma da Monica,5,Turma do Chaves,6,Tutorial,116,Tv,1,Unhas,5,Unicornio,82,Ursinho,112,Ursinho Pooh,8,Ursinhos,536,Urso,42,Vaca,16,Veados,3,Vegetais,2,Veiculos,3,Velcro,1,Ver e Fazer,3,Verduras,2,Vestido,32,vide,1,Video,50,Videos,721,Visual,1,Wallpaper,27,web-stories,3,Youtube,22,Zebra,4,Zumbi,19,
ltr
item
Ver e Fazer: 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
https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSkTPmmyig5jwQg_RvrLYBNWbWwVjaRDah9Kewr-kEmuGS0Sj4ADrVjGQ2ZXFMzQHt6PSWtXmr4fa6f3IclsPmk57F0GP4Q0d-MvdJIJG_VNH48bHOj6ivXE91Y_6ZLmM7Zz_acHoaq3ytzG9fuv-eNYuZpwhJM652KYJIHuuTPEE5L8VC70gsawqz0j4/s320/golang-sqlc-mysql-migrate-bootstrap-criarsite.png
https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSkTPmmyig5jwQg_RvrLYBNWbWwVjaRDah9Kewr-kEmuGS0Sj4ADrVjGQ2ZXFMzQHt6PSWtXmr4fa6f3IclsPmk57F0GP4Q0d-MvdJIJG_VNH48bHOj6ivXE91Y_6ZLmM7Zz_acHoaq3ytzG9fuv-eNYuZpwhJM652KYJIHuuTPEE5L8VC70gsawqz0j4/s72-c/golang-sqlc-mysql-migrate-bootstrap-criarsite.png
Ver e Fazer
https://www.verefazer.org/2024/01/criar-site-com-upload-de-imagem-migrate-sqlc-go-mysql.html
https://www.verefazer.org/
https://www.verefazer.org/
https://www.verefazer.org/2024/01/criar-site-com-upload-de-imagem-migrate-sqlc-go-mysql.html
true
4084861044654339844
UTF-8
Loaded All Posts Not found any posts VER TODOS Ver mais Reply Cancel reply Delete By Home PAGES POSTS View All Veja também LABEL ARCHIVE SEARCH ALL POSTS Not found any post match with your request Back Home Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sun Mon Tue Wed Thu Fri Sat January February March April May June July August September October November December Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec just now 1 minute ago $$1$$ minutes ago 1 hour ago $$1$$ hours ago Yesterday $$1$$ days ago $$1$$ weeks ago more than 5 weeks ago Followers Follow Conteúdo Exclusivo Por favor, compartilhe para desbloquear Copy All Code Select All Code All codes were copied to your clipboard Can not copy the codes / texts, please press [CTRL]+[C] (or CMD+C with Mac) to copy